Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -17,3 +17,6 @@ NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/ap
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
|
||||
|
||||
|
||||
|
||||
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
|
||||
|
||||
@@ -20,3 +20,6 @@ NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/ap
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
|
||||
|
||||
|
||||
|
||||
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
|
||||
|
||||
@@ -16,3 +16,6 @@ NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BE
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api
|
||||
|
||||
|
||||
|
||||
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=
|
||||
|
||||
@@ -15,4 +15,40 @@ export const sanitizeInput = (val: string, allowDecimal = false) => {
|
||||
|
||||
// 정수만 허용
|
||||
return val.replace(/[^0-9]/g, "");
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
// utils: format Date <-> "YYYY-MM-DD" without moment
|
||||
export const formatDateToYMD=(value: Date): string => {
|
||||
if (!value) return "";
|
||||
// value can be Date | string ("YYYY-MM-DD")
|
||||
if (typeof value === "string") {
|
||||
// assume already in yyyy-mm-dd
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
||||
const d = new Date(value);
|
||||
if (isNaN(d.getTime())) return "";
|
||||
return toYMD(d);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
if (isNaN(value.getTime())) return "";
|
||||
return toYMD(value);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const toYMD=(date: Date): string => {
|
||||
// avoid timezone shift by normalizing to local date parts
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
// const parseYMDToDate=(ymd: string): Date | null => {
|
||||
// if (!/^\d{4}-\d{2}-\d{2}$/.test(ymd)) return null;
|
||||
// const [y, m, d] = ymd.split("-").map(Number);
|
||||
// const date = new Date(y, (m - 1), d);
|
||||
// // validate round-trip
|
||||
// return (date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d) ? date : null;
|
||||
// }
|
||||
@@ -2,7 +2,7 @@ import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/mod
|
||||
import { useDmStore } from "@/app/store";
|
||||
import {
|
||||
CANDIDATE,
|
||||
DatePicker,
|
||||
// DatePicker,
|
||||
FormField,
|
||||
FormLabel,
|
||||
FormUi,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
UiModal,
|
||||
} from "@common/business_components";
|
||||
import { Input } from "@common/components";
|
||||
import { Button, Label } from "@common/components/ui";
|
||||
import { Button, Label } from "@common/components/ui";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { AccessibleFormField } from "./AccessibleFormField";
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from "react-hook-form";
|
||||
import SearchInputText from "../common/searhDemandInput/component";
|
||||
import React from "react";
|
||||
import { sanitizeInput } from "../common/utils/utils";
|
||||
import { formatDateToYMD, sanitizeInput } from "../common/utils/utils";
|
||||
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
|
||||
import type { SdmInfoFormData } from "@/app/store/sdmMng";
|
||||
|
||||
@@ -161,7 +161,9 @@ export function SdmInfoRegForm({
|
||||
|
||||
if (defaults) {
|
||||
Object.entries(defaults).forEach(([field, value]) => {
|
||||
form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true });
|
||||
form.setValue(field as keyof SdmInfoFormData, value, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [form.getValues("usePurpose")]);
|
||||
@@ -208,8 +210,8 @@ export function SdmInfoRegForm({
|
||||
formIndex: 0,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<div className="">
|
||||
<DatePicker
|
||||
<div className="relative w-64">
|
||||
{/* <DatePicker
|
||||
mode="single"
|
||||
selected={field.value as Date}
|
||||
onSelect={(date) => {
|
||||
@@ -224,8 +226,18 @@ export function SdmInfoRegForm({
|
||||
},
|
||||
}}
|
||||
showOutsideDays={true}
|
||||
selectClass={undefined}
|
||||
/>
|
||||
selectClass={undefined}
|
||||
/> */}
|
||||
{/* 네이티브 input */}
|
||||
<Input
|
||||
className="w-full !text-xl text-right"
|
||||
type="date"
|
||||
onChange= {(e) => {
|
||||
const selectedDate = e.target.value ? new Date(e.target.value) : null;
|
||||
field.onChange(selectedDate);
|
||||
}}
|
||||
value={ formatDateToYMD(field.value as Date) }
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -363,13 +375,11 @@ export function SdmInfoRegForm({
|
||||
render={({ field }) =>
|
||||
name === "money" ? (
|
||||
<div>
|
||||
<Label className="text-black" >
|
||||
희망단가(원)*
|
||||
</Label>
|
||||
{ component(field)}
|
||||
<Label className="text-black">희망단가(원)*</Label>
|
||||
{component(field)}
|
||||
</div>
|
||||
// <FormUi label={label + "*"}>{component(field)}</FormUi>
|
||||
) : (
|
||||
// <FormUi label={label + "*"}>{component(field)}</FormUi>
|
||||
<AccessibleFormField
|
||||
field={field}
|
||||
label={label}
|
||||
@@ -773,7 +783,7 @@ export function SdmInfoRegForm({
|
||||
onSelectValue={(val: string | number) => {
|
||||
field.onChange(val.toString());
|
||||
}}
|
||||
value={field.value }
|
||||
value={field.value}
|
||||
readOnly={isDisabled}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
|
||||
+54
-29
@@ -7,6 +7,8 @@ const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_URL;
|
||||
const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
|
||||
const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
|
||||
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || "";
|
||||
const AdressKey = process.env.NEXT_PUBLIC_WR_ADDRESS_KEY || "";
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
@@ -24,8 +26,8 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
if (pathname.startsWith("/server")) {
|
||||
const decodedParams = Array.from(searchParams.entries())
|
||||
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
|
||||
.join("&");
|
||||
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
|
||||
.join("&");
|
||||
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
decodedParams ? `?${decodedParams}` : ""
|
||||
@@ -38,7 +40,6 @@ export function middleware(req: NextRequest) {
|
||||
return NextResponse.redirect(new URL("/pages/wrStatus", req.url));
|
||||
}
|
||||
|
||||
|
||||
if (pathname.startsWith("/attach-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
// const token =
|
||||
@@ -69,7 +70,7 @@ export function middleware(req: NextRequest) {
|
||||
`[Middleware] Rewriting from '/user-service' path to: ${newUrl}`
|
||||
);
|
||||
return NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders
|
||||
headers: requestHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,33 +82,31 @@ export function middleware(req: NextRequest) {
|
||||
let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
|
||||
|
||||
if (
|
||||
typename == "hasudo:tb_il_swage_prcs_fclt" //하수처리시설
|
||||
|| typename == "hasudo:tb_ms_swage_prcs_zone" //하수처리구역
|
||||
|| typename == "hasudo:tb_ms_sbmrg_dngr_map" //침수위험지도
|
||||
|| typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도
|
||||
typename == "hasudo:tb_il_swage_prcs_fclt" || //하수처리시설
|
||||
typename == "hasudo:tb_ms_swage_prcs_zone" || //하수처리구역
|
||||
typename == "hasudo:tb_ms_sbmrg_dngr_map" || //침수위험지도
|
||||
typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도
|
||||
) {
|
||||
newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
|
||||
}
|
||||
else if (
|
||||
typename == "hasudo:tb_ms_swage_ppl" //관로
|
||||
|| typename == "hasudo:tb_ms_swage_mhl" //맨홀
|
||||
|| typename == "hasudo:tb_ms_swage_pmstn" //펌프장
|
||||
|| typename == "hasudo:tb_ms_stofcbr" //우수토실
|
||||
} else if (
|
||||
typename == "hasudo:tb_ms_swage_ppl" || //관로
|
||||
typename == "hasudo:tb_ms_swage_mhl" || //맨홀
|
||||
typename == "hasudo:tb_ms_swage_pmstn" || //펌프장
|
||||
typename == "hasudo:tb_ms_stofcbr" //우수토실
|
||||
) {
|
||||
newUrl = `${LX_GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
|
||||
}
|
||||
else if (
|
||||
typename == "hasudo:om_dt_untrt_swg_rain" //중점우수토실
|
||||
|| typename == "hasudo:om_wqlt_station" //관측소
|
||||
|| typename == "hasudo:tb_wr_bdst_ruse_fclt" //재이용시설
|
||||
|| typename == "hasudo:tb_wr_rnwtr_utztn_fclt" //빗물이용시설
|
||||
|| typename == "hasudo:tb_wr_rswtr_fclt" //중수도시설
|
||||
|| typename == "hasudo:tb_wr_htdfl_fclt" //온배수시설
|
||||
|| typename == "hasudo:tb_wr_ruse_fclt_dmd_co" //수요처
|
||||
|| typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처
|
||||
){
|
||||
} else if (
|
||||
typename == "hasudo:om_dt_untrt_swg_rain" || //중점우수토실
|
||||
typename == "hasudo:om_wqlt_station" || //관측소
|
||||
typename == "hasudo:tb_wr_bdst_ruse_fclt" || //재이용시설
|
||||
typename == "hasudo:tb_wr_rnwtr_utztn_fclt" || //빗물이용시설
|
||||
typename == "hasudo:tb_wr_rswtr_fclt" || //중수도시설
|
||||
typename == "hasudo:tb_wr_htdfl_fclt" || //온배수시설
|
||||
typename == "hasudo:tb_wr_ruse_fclt_dmd_co" || //수요처
|
||||
typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처
|
||||
) {
|
||||
const url = req.nextUrl.clone();
|
||||
url.searchParams.set('TYPENAME', typename.replace("hasudo:",""));
|
||||
url.searchParams.set("TYPENAME", typename.replace("hasudo:", ""));
|
||||
queryParams = url.searchParams.toString();
|
||||
newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`;
|
||||
}
|
||||
@@ -118,15 +117,41 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
if (pathname.startsWith("/sewerage-service")) {
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
if (pathname.startsWith("/bussiness-juso-service")) {
|
||||
console.log("AdressKey ", AdressKey);
|
||||
const params = new URLSearchParams({
|
||||
currentPage: "1", // 원하는 페이지
|
||||
countPerPage: "5", // 페이지당 결과 수
|
||||
keyword: searchParams.get("keyword") || "", // 검색어(주소)
|
||||
confmKey: AdressKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯
|
||||
resultType: "json", // 결과 형식 기본은 xml
|
||||
});
|
||||
const newUrl = `${BUSINESS_JUSO_API_URL}?${params.toString()}`;
|
||||
console.log(`[Middleware] Request to business.juso.go.kr`);
|
||||
console.log(req.url);
|
||||
console.log(
|
||||
`[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}`
|
||||
);
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*", "/user-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", "/"],
|
||||
matcher: [
|
||||
"/server/:path*",
|
||||
"/user-service/:path*",
|
||||
"/attach-service/:path*",
|
||||
"/geoserver/:path*",
|
||||
"/sewerage-service/:path*",
|
||||
"/bussiness-juso-service/:path*",
|
||||
"/",
|
||||
],
|
||||
};
|
||||
|
||||
+104
-83
@@ -12,8 +12,8 @@ import RdiiChart from './chart/rdiiChart';
|
||||
import { useInfilChartModalStore, useRdiiChartModalStore } from "./chartModalStore";
|
||||
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
|
||||
import { codeMapper } from "../common/codeMapper";
|
||||
import { searchUpperPpls } from '@common/business_components/ui/map/Gis';
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar';
|
||||
import { searchUpperPpls } from "./smh";
|
||||
|
||||
|
||||
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
|
||||
@@ -58,7 +58,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
const getSelectedLayers = () => {
|
||||
let result = null;
|
||||
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
|
||||
let featureLayer: { get: (key: string) => any }; // 피처가 속한 레이어를 찾기 위한 변수
|
||||
let featureLayer: any = null; // 피처가 속한 레이어를 찾기 위한 변수
|
||||
layers.some((layer: any) => {
|
||||
const source = layer.getSource();
|
||||
// if (source && featureData && featureData.fid) {
|
||||
@@ -77,98 +77,119 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
setIsClicked(true);
|
||||
console.log('gis data', featureData.values_);
|
||||
const lyrTyNm = featureData.id_.split('.')[0];
|
||||
if (lyrTyNm === 'tb_ms_swage_ppl') {
|
||||
result = '하수관로';
|
||||
setHeadlist(MsSwagePplCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
}
|
||||
else if (lyrTyNm === 'tb_ms_swage_mhl') {
|
||||
result = '하수맨홀';
|
||||
setHeadlist(MsSwageMhlCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
}
|
||||
else if (lyrTyNm === 'tb_ms_swage_pmstn') { // 하수펌프장
|
||||
const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장');
|
||||
if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) {
|
||||
setIsDrtwSewagePump(true);
|
||||
setHeadlist(DrtwSewagePumpCol);
|
||||
|
||||
switch (lyrTyNm) {
|
||||
case 'tb_ms_swage_ppl': {
|
||||
result = '하수관로';
|
||||
setHeadlist(MsSwagePplCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
setIsDrtwSewagePump(false);
|
||||
setHeadlist(ExclnSewagePumpCol);
|
||||
case 'tb_ms_swage_mhl': {
|
||||
result = '하수맨홀';
|
||||
setHeadlist(MsSwageMhlCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_swage_pmstn': {
|
||||
const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장');
|
||||
if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) {
|
||||
setIsDrtwSewagePump(true);
|
||||
setHeadlist(DrtwSewagePumpCol);
|
||||
}
|
||||
else {
|
||||
setIsDrtwSewagePump(false);
|
||||
setHeadlist(ExclnSewagePumpCol);
|
||||
}
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
result = '하수펌프장';
|
||||
break;
|
||||
}
|
||||
case 'tb_il_swage_prcs_fclt': {
|
||||
result = '하수처리시설';
|
||||
setHeadlist(SwtrmtFcltCol);
|
||||
setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_swage_prcs_zone': {
|
||||
result = '하수처리구역';
|
||||
setHeadlist(SwtrmtPrcsZoneCol);
|
||||
setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_stofcbr': {
|
||||
result = '우수토실';
|
||||
setHeadlist(stofcbrCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
break;
|
||||
}
|
||||
case 'om_dt_untrt_swg_rain': {
|
||||
result = '중점우수토실';
|
||||
setHeadlist(untrtSwgRainCol1);
|
||||
setHeadlist2(untrtSwgRainCol2);
|
||||
setLayerSpecificNm(featureData.values_.manage_no);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
result = featureLayer ? featureLayer.get('lyrNm') : null;
|
||||
break;
|
||||
}
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
result = '하수펌프장';
|
||||
}
|
||||
else if (lyrTyNm === 'tb_il_swage_prcs_fclt') {
|
||||
result = '하수처리시설';
|
||||
setHeadlist(SwtrmtFcltCol);
|
||||
setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd);
|
||||
}
|
||||
else if (lyrTyNm === 'tb_ms_swage_prcs_zone') {
|
||||
result = '하수처리구역';
|
||||
setHeadlist(SwtrmtPrcsZoneCol);
|
||||
setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd);
|
||||
}
|
||||
else if (lyrTyNm === 'tb_ms_stofcbr') {
|
||||
result = '우수토실';
|
||||
setHeadlist(stofcbrCol);
|
||||
setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
}
|
||||
else if (lyrTyNm === 'om_dt_untrt_swg_rain') {
|
||||
result = '중점우수토실';
|
||||
setHeadlist(untrtSwgRainCol1);
|
||||
setHeadlist2(untrtSwgRainCol2);
|
||||
setLayerSpecificNm(featureData.values_.manage_no);
|
||||
}
|
||||
else result = featureLayer.get('lyrNm');
|
||||
}
|
||||
else {
|
||||
// SearchModal 클릭 이벤트
|
||||
setIsClicked(false);
|
||||
console.log('SearchModal data', featureData);
|
||||
const lyrTyNm = featureData.lyrTypeNm;
|
||||
if (featureData.lyrTypeNm === 'tb_ms_swage_ppl') {
|
||||
result = '하수관로';
|
||||
setHeadlist(MsSwagePplCol2)
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
}
|
||||
else if (featureData.lyrTypeNm === 'tb_ms_swage_mhl') {
|
||||
result = '하수맨홀';
|
||||
setHeadlist(MsSwageMhlCol2)
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
}
|
||||
else if (featureData.lyrTypeNm === 'tb_ms_swage_pmstn') { // 하수펌프장
|
||||
const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장');
|
||||
if (drtwPumpCode && featureData.usgCd.trim() === drtwPumpCode.val) {
|
||||
console.log('오수펌프장');
|
||||
setIsDrtwSewagePump(false);
|
||||
setHeadlist(ExclnSewagePumpCol2)
|
||||
|
||||
switch (lyrTyNm) {
|
||||
case 'tb_ms_swage_ppl': {
|
||||
result = '하수관로';
|
||||
setHeadlist(MsSwagePplCol2)
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
console.log('빗물펌프장');
|
||||
setIsDrtwSewagePump(true);
|
||||
setHeadlist(DrtwSewagePumpCol2)
|
||||
case 'tb_ms_swage_mhl': {
|
||||
result = '하수맨홀';
|
||||
setHeadlist(MsSwageMhlCol2)
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_swage_pmstn': {
|
||||
const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장');
|
||||
if (drtwPumpCode && featureData.usgCd.trim() === drtwPumpCode.val) {
|
||||
console.log('오수펌프장');
|
||||
setIsDrtwSewagePump(false);
|
||||
setHeadlist(ExclnSewagePumpCol2)
|
||||
}
|
||||
else {
|
||||
console.log('빗물펌프장');
|
||||
setIsDrtwSewagePump(true);
|
||||
setHeadlist(DrtwSewagePumpCol2)
|
||||
}
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
result = '하수펌프장';
|
||||
break;
|
||||
}
|
||||
case 'tb_il_swage_prcs_fclt': {
|
||||
result = '하수처리시설';
|
||||
setHeadlist(SwtrmtFcltCol2)
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_swage_prcs_zone': {
|
||||
result = '하수처리구역';
|
||||
setHeadlist(SwtrmtPrcsZoneCol2);
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
break;
|
||||
}
|
||||
case 'tb_ms_stofcbr': {
|
||||
result = '우수토실';
|
||||
setHeadlist(stofcbrCol2)
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
break;
|
||||
}
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
result = '하수펌프장';
|
||||
}
|
||||
else if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') {
|
||||
result = '하수처리시설';
|
||||
setHeadlist(SwtrmtFcltCol2)
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
}
|
||||
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') {
|
||||
result = '하수처리구역';
|
||||
setHeadlist(SwtrmtPrcsZoneCol2);
|
||||
setLayerSpecificNm(featureData.swtrmtFcltCd);
|
||||
}
|
||||
else if (featureData.lyrTypeNm === 'tb_ms_stofcbr') {
|
||||
result = '우수토실';
|
||||
setHeadlist(stofcbrCol2)
|
||||
setLayerSpecificNm(featureData.fcltMngNo);
|
||||
}
|
||||
// else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실';
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -424,7 +445,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
className="px-2 py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700 mr-4"
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
searchUpperPpls(layerSpecificNm).finally(() => setIsLoading(false));
|
||||
searchUpperPpls(gisMap, layerSpecificNm).finally(() => setIsLoading(false));
|
||||
}}
|
||||
>
|
||||
상류관로 보기
|
||||
|
||||
@@ -1,6 +1,84 @@
|
||||
// 하수맨홀
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import { useEffect, useState } from 'react';
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
|
||||
// 상류관로 보기 함수 (FeatureClickEventPopup에서 사용)
|
||||
export async function searchUpperPpls(gisMap: any, fcltMngNo: any) {
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
let data: any = null;
|
||||
if (res.ok) {
|
||||
try {
|
||||
// 응답이 비어있는지 확인
|
||||
const contentLength = res.headers.get("content-length");
|
||||
if (contentLength === "0") {
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
|
||||
const text = await res.text();
|
||||
if (!text || text.trim() === "") {
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// JSON 파싱 시도
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error("JSON parsing error:", error);
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const select: any = GisApp.Module.select;
|
||||
let foundFeature: any = null;
|
||||
|
||||
(data || []).forEach((item: any) => {
|
||||
const pplMngNo = item.ppl_mng_no?.trim?.();
|
||||
|
||||
gisMap
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.filter((e: any) => e.get("lyrTyNm") == "tb_ms_swage_ppl")
|
||||
.forEach((layer: any) => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
for (const f of features) {
|
||||
if (f.values_.fclt_mng_no?.trim?.() === pplMngNo) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) select?.selectMultiFeature?.(foundFeature, true);
|
||||
});
|
||||
|
||||
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
const geometry = foundFeature?.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
gisMap.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 14,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const inputColumn = [
|
||||
{ id: "swtrmtZoneNm", title: "처리구역", type: "input" },
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import SearchModal from "./searchModal/searchModal";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import { WaterDepth, WATER_DEPTH_LAYER_TYPES, ORDERED_LAYERS } from "./waterDepth/waterDepth";
|
||||
import { WaterDepth, WATER_DEPTH_LAYER_TYPES } from "./waterDepth/waterDepth";
|
||||
import { ProcSearchBar } from "@common/business_components/ui/map/searchBar";
|
||||
|
||||
// 타입 정의 추가
|
||||
@@ -160,7 +160,6 @@ const Map = () => {
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying as any}
|
||||
targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any}
|
||||
orderedLayers={ORDERED_LAYERS as any}
|
||||
checkInterval={2500}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +149,6 @@ const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sg
|
||||
// if (gubun !== "") { // 구분검색 (추후 사용할 수도 있을 것 같아 남겨둠)
|
||||
// filterReportData = filterReportData.filter(item => (item.plantZoneGubun) ? String(item.plantZoneGubun).includes(String(gubun)) : null);
|
||||
// }
|
||||
console.log('report count:', filterReportData.length);
|
||||
|
||||
setTableCount(filterReportData.length || 0);
|
||||
|
||||
|
||||
@@ -80,12 +80,9 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const [end2, setEnd2] = useState("");
|
||||
const [var1, setVar1] = useState("");
|
||||
const [var2, setVar2] = useState("");
|
||||
const [var3, setVar3] = useState("");
|
||||
const [var4, setVar4] = useState("");
|
||||
// 시작, 종료
|
||||
const [yearFilter, setYearFilter] = useState({ start: "", end: "" });
|
||||
|
||||
const { ctpv, sgg, BIZ_TY_CD, BIZ_SE_CD } = useStore();
|
||||
const { ctpv, sgg, BIZ_TY_CD } = useStore();
|
||||
|
||||
|
||||
// 오늘 날짜 구하기
|
||||
@@ -128,8 +125,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
setEnd2("");
|
||||
setVar1("");
|
||||
setVar2("");
|
||||
setVar3("");
|
||||
setVar4("");
|
||||
setCtpvCd("0");
|
||||
setSggCd("0");
|
||||
setYearFilter({
|
||||
@@ -196,7 +191,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
|
||||
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
|
||||
console.log('api:', `${API_URL}/v1/fclts?${queryParams.toString()}`);
|
||||
|
||||
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
|
||||
const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => {
|
||||
@@ -231,7 +225,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
console.log("dataWithRowNumber: ", dataWithRowNumber)
|
||||
setTableData(dataWithRowNumber);
|
||||
setTableCount(res.data.count || []);
|
||||
|
||||
@@ -264,7 +257,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const params = new URLSearchParams(RequestDTO).toString();
|
||||
|
||||
const res = await getData(`v1/bizInfo?${params}`);
|
||||
console.log('운영정보 사업 데이터:', res.content);
|
||||
|
||||
const mappedContent = (res.content || []).map((item: any, index: number) => {
|
||||
const formattedItem = formatDisplayData(item);
|
||||
@@ -512,7 +504,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
|
||||
|
||||
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
|
||||
console.log('운영정보 cellClick 데이터:', res.data);
|
||||
|
||||
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
|
||||
const formattedItem = formatDisplayData(item);
|
||||
@@ -527,7 +518,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
|
||||
};
|
||||
});
|
||||
console.log('운영정보 cellClick 데이터:', mappedData);
|
||||
|
||||
setDetailTableData(mappedData);
|
||||
setDetailTableCount(res.count || 0);
|
||||
@@ -565,7 +555,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
swtrmt_zone_nm: item.swtrmt_zone_nm ? item.swtrmt_zone_nm : '-',
|
||||
};
|
||||
});
|
||||
console.log('운영정보 PageNation 데이터:', mappedData);
|
||||
|
||||
setDetailTableData(mappedData);
|
||||
setDetailTableCount(res.count || 0);
|
||||
@@ -667,7 +656,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
|
||||
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
|
||||
console.log('res.data.data', res.data.data);
|
||||
|
||||
|
||||
|
||||
@@ -785,7 +773,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
|
||||
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
|
||||
const excelData = mappedContent; // 배열로 감싸서 전달
|
||||
console.log('excelData', excelData);
|
||||
|
||||
// 파일명 생성 (분석번호 기반)
|
||||
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
|
||||
@@ -929,8 +916,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const apiStartTime = performance.now();
|
||||
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
|
||||
const apiEndTime = performance.now();
|
||||
console.log(`🚀 1. API 호출 시간: ${(apiEndTime - apiStartTime).toFixed(2)}ms`);
|
||||
console.log('운영정보 결과 다운로드.data:', res.data);
|
||||
|
||||
const mappingStartTime = performance.now();
|
||||
// 헤더 매핑 정의
|
||||
@@ -974,13 +959,10 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
};
|
||||
});
|
||||
const mappingEndTime = performance.now();
|
||||
console.log(`🔄 2. 데이터 매핑 시간: ${(mappingEndTime - mappingStartTime).toFixed(2)}ms`);
|
||||
|
||||
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
|
||||
const excelData = mappedData; // 배열로 감싸서 전달
|
||||
|
||||
console.log('excelData', excelData);
|
||||
|
||||
const excelStartTime = performance.now();
|
||||
// 파일명 생성 (분석번호 기반)
|
||||
const label = WORK_TYPES.find((type) => type.val === workType)?.text
|
||||
@@ -994,7 +976,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
headerMapping: headerMapping
|
||||
});
|
||||
const excelEndTime = performance.now();
|
||||
console.log(`📊 3. Excel 다운로드 시간: ${(excelEndTime - excelStartTime).toFixed(2)}ms`);
|
||||
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
@@ -1023,7 +1004,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
setIsLoading(true);
|
||||
|
||||
const row = detailTableData[0];
|
||||
console.log('row', row);
|
||||
const layerNm = row.lyrTypeNm;
|
||||
const fids = detailTableData.map((item) => item.fid);
|
||||
const core = GisApp.Module.layer as any;
|
||||
@@ -1116,6 +1096,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
onPageChange={setCurPage}
|
||||
resetPageNo={isResetPage}
|
||||
resetSelection={isResetSelection}
|
||||
curPage={curPage}
|
||||
headButton={
|
||||
<div className='flex'>
|
||||
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button>
|
||||
@@ -1147,6 +1128,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
onPageChange={setCurPage}
|
||||
resetPageNo={isResetPage}
|
||||
resetSelection={isResetSelection}
|
||||
curPage={curPage}
|
||||
headButton={
|
||||
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-sm font-medium green" size="sm" onClick={operationExcelDownload}>파일 다운로드</Button>
|
||||
}
|
||||
@@ -1173,6 +1155,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
onPageChange={setDetailCurPage}
|
||||
resetPageNo={isResetDetailPage}
|
||||
resetSelection={isResetDetailSelection}
|
||||
curPage={detailCurPage}
|
||||
headButton={
|
||||
<div className='flex'>
|
||||
<Button className="px-4 py-2 !mr-[1.2rem] text-white rounded-sm text-lg font-medium primary" size="sm" onClick={viewMultipleFeature}>지도에서 보기</Button>
|
||||
|
||||
@@ -7,16 +7,6 @@ export const WATER_DEPTH_LAYER_TYPES: string[] = [
|
||||
'tb_ms_flood_dngr_map',
|
||||
'tb_ms_sbmrg_dngr_map'
|
||||
];
|
||||
// 지정된 순서에 따른 lyr_sn 배열
|
||||
export const ORDERED_LAYERS: string[] = [
|
||||
'27', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도
|
||||
'28', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도
|
||||
'3', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도
|
||||
'4', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도
|
||||
'59', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도
|
||||
'60', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도
|
||||
'58' // 도시침수지도(tb_ms_flood_dngr_map) - 80년 빈도
|
||||
];
|
||||
|
||||
interface WaterDepthProps {
|
||||
onClose?: () => void;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { TableHead, TableRow } from "@common/components/ui";
|
||||
|
||||
|
||||
export type TableUpperProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
upName?: string;
|
||||
upSequnce?: number;
|
||||
edit?: boolean;
|
||||
visible?: boolean;
|
||||
rowSpan?: number;
|
||||
colSpan?: number;
|
||||
type?: string;
|
||||
selectData?: string; // type 이 seelct 일경우 selectarray 정하기 위해
|
||||
row?: number;
|
||||
mergeCell?: boolean // 셀을 합칠경우
|
||||
maxLength?: number
|
||||
readonly?: boolean
|
||||
};
|
||||
type chHeadType = {
|
||||
headNm: string;
|
||||
text: string;
|
||||
length: number;
|
||||
rows?: number;
|
||||
};
|
||||
|
||||
export const HeadMakeFloodPre = (
|
||||
upHeadList: TableUpperProps[],
|
||||
makeHeadNm: chHeadType[]
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
<TableRow>
|
||||
{upHeadList
|
||||
.filter((data) => data.visible != false)
|
||||
.map((head: TableUpperProps) =>
|
||||
!head.upSequnce ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
rowSpan={2}
|
||||
key={head.id}
|
||||
>
|
||||
{head.title}
|
||||
</TableHead>
|
||||
) : head.upSequnce === 1 ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
key={head.id}
|
||||
rowSpan={1}
|
||||
colSpan={
|
||||
makeHeadNm?.find((ch) => ch.headNm === head.upName)?.length
|
||||
}
|
||||
>
|
||||
{makeHeadNm?.find((ch) => ch.headNm === head.upName)?.text}
|
||||
</TableHead>
|
||||
) : null
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
{upHeadList
|
||||
.filter((data) => data.visible != false)
|
||||
.map((head: TableUpperProps) =>
|
||||
makeHeadNm?.find((ch) => ch.headNm === head.upName) ? (
|
||||
<TableHead
|
||||
className={`border border-gray-600 px-2 py-1 `}
|
||||
key={head.id}
|
||||
>
|
||||
{head.title}
|
||||
</TableHead>
|
||||
) : null
|
||||
)}
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,200 @@
|
||||
import type { TableUpperProps } from "./headMakeFloodPre";
|
||||
import { Input, TableCell, TableRow } from "@common/components/ui";
|
||||
|
||||
|
||||
// 숫자입력시 , 삽입
|
||||
const formatCommaNumber = (
|
||||
value: number | string | undefined | null
|
||||
): string => {
|
||||
if (value === undefined || value === null || value === "") return "";
|
||||
const num = Number(value);
|
||||
// 숫자로 변환할 수 없는 값이 들어올 경우 빈 문자열을 반환하여 예상치 못한 "NaN" 출력을 방지합니다.
|
||||
if (isNaN(num)) {
|
||||
return "";
|
||||
}
|
||||
return num.toLocaleString();
|
||||
};
|
||||
|
||||
// onchange 텍스트를 숫자로 and ,제거
|
||||
const handleCommaNumberChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
onChange: (value: number | "") => void
|
||||
) => {
|
||||
const raw = e.target.value.replace(/,/g, "");
|
||||
if (!/^\d*$/.test(raw)) return;
|
||||
onChange(raw === "" ? "" : Number(raw));
|
||||
};
|
||||
|
||||
const handleChange = (
|
||||
rowIndex: number,
|
||||
field: any,
|
||||
value: string,
|
||||
setInputData: (data: any) => void
|
||||
) => {
|
||||
if (Number(value) < 0 || !Number(value)) {
|
||||
value = "0";
|
||||
}
|
||||
setInputData((prevData: any) =>
|
||||
prevData?.map((row: any, index: number) => {
|
||||
if (index === rowIndex) {
|
||||
return {
|
||||
...row,
|
||||
[field]: Number(value.replace(/,/g, "")),
|
||||
};
|
||||
}
|
||||
return row;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const InputTableBody = (
|
||||
chgRes: any[] | [],
|
||||
headList: TableUpperProps[],
|
||||
setInputData: (data: any[]) => void,
|
||||
isEditable: boolean = true
|
||||
) => {
|
||||
// mergeCell 처리용
|
||||
const getRowSpan = (data: any[], key: string, rowIndex: number) => {
|
||||
if (rowIndex > 0 && data[rowIndex - 1][key] && String(data[rowIndex][key]).trim() === "") {
|
||||
return 0; // 이전과 같으면 셀을 그리지 않음
|
||||
}
|
||||
let span = 1;
|
||||
for (let i = rowIndex + 1; i < data.length; i++) {
|
||||
if (data[i][key] === data[rowIndex][key]) span++;
|
||||
else break;
|
||||
}
|
||||
return span;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{chgRes.map((data, dataIndex) => {
|
||||
// 소계/총합계 행 판별
|
||||
const isSubTotal = data["용수명"] === "소계";
|
||||
const isTotal = data["구분"] === 0;
|
||||
|
||||
const bgClass = isTotal
|
||||
? "bg-[#d5e1eb] font-bold"
|
||||
: isSubTotal
|
||||
? "bg-[#e6eef5] font-semibold"
|
||||
: "";
|
||||
|
||||
return (
|
||||
<TableRow key={dataIndex + "row"}>
|
||||
{headList.map((head, headIndex) => {
|
||||
// console.log('head, head.id', head, head.id);
|
||||
const isTextColumn = head.id === "__EMPTY_3";
|
||||
const textAlignClass = isTextColumn
|
||||
? "text-center"
|
||||
: "text-right";
|
||||
|
||||
// // 총합계 행이면 "구분"에서 colSpan=2로 병합
|
||||
// if (
|
||||
// (data["구분"] === 0 || data["용수명"] === 0) &&
|
||||
// head.id === "구분"
|
||||
// ) {
|
||||
// return (
|
||||
// <TableCell
|
||||
// key={head.id + headIndex}
|
||||
// colSpan={2}
|
||||
// className="font-bold text-center bg-[#d5e1eb]"
|
||||
// >
|
||||
// 총합계
|
||||
// </TableCell>
|
||||
// );
|
||||
// }
|
||||
// // 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음
|
||||
// if (
|
||||
// (data["구분"] === 0 || data["용수명"] === 0) &&
|
||||
// head.id === "용수명"
|
||||
// ) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // 총합계 행의 나머지 셀에도 색상 적용
|
||||
// if (data["구분"] === 0 || data["용수명"] === 0) {
|
||||
// return (
|
||||
// <TableCell
|
||||
// key={head.id + headIndex}
|
||||
// className="bg-[#d5e1eb] font-bold text-right"
|
||||
// >
|
||||
// {Number(data[head.id])
|
||||
// ? formatCommaNumber(data[head.id as keyof any])
|
||||
// : (data[head.id as keyof any] ?? "")}
|
||||
// </TableCell>
|
||||
// );
|
||||
// }
|
||||
// mergeCell 처리
|
||||
if (head.mergeCell === true) {
|
||||
const rowSpan = getRowSpan(chgRes, head.id, dataIndex);
|
||||
if (rowSpan === 0) return null;
|
||||
return (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
rowSpan={rowSpan}
|
||||
className={`w-36 align-middle text-center whitespace-nowrap ${bgClass}`}
|
||||
>
|
||||
{Number(data[head.id])
|
||||
? formatCommaNumber(data[head.id as keyof any])
|
||||
: (data[head.id as keyof any] ?? "")}
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
// 기본 셀
|
||||
// return head.edit === true && data.edit !== false ? (
|
||||
return isEditable && head.edit === true && data.edit !== false ? (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
className={`w-36 ${bgClass}`}
|
||||
>
|
||||
<Input
|
||||
// className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`}
|
||||
className={`${textAlignClass} !text-xl h-10 w-full`}
|
||||
onChange={(e) =>
|
||||
handleCommaNumberChange(e, (val) =>
|
||||
handleChange(
|
||||
dataIndex,
|
||||
head.id,
|
||||
String(val),
|
||||
setInputData
|
||||
)
|
||||
)
|
||||
}
|
||||
value={formatCommaNumber(data[head.id as keyof any]) ?? ""}
|
||||
maxLength={12}
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell
|
||||
key={head.id + headIndex}
|
||||
className={`w-36 ${bgClass}`}
|
||||
>
|
||||
<div
|
||||
// className={`
|
||||
// ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"}
|
||||
// !text-xl w-full h-10
|
||||
// `}
|
||||
className={`${textAlignClass} !text-xl w-full h-10`}
|
||||
>
|
||||
{Number(data[head.id])
|
||||
? formatCommaNumber(data[head.id as keyof any])
|
||||
: (data[head.id as keyof any] ?? "")}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{chgRes.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={headList.length}
|
||||
className="h-24 text-center text-gray-500"
|
||||
>
|
||||
조회된 결과가 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,10 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
import { UITab, UiTable } from '@common/business_components/ui'
|
||||
import { Table, TableBody, TableHeader } from "@common/components/ui";
|
||||
import * as XLSX from 'xlsx';
|
||||
import { HeadMakeFloodPre } from './headMakeFloodPre';
|
||||
import { InputTableBody } from './inputTableBody';
|
||||
|
||||
const TABS = [
|
||||
{ val: "management", text: "관리SHEET" },
|
||||
@@ -19,7 +22,7 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "management");
|
||||
const [managementTableHeader, setManagementTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더
|
||||
const [managementTableData, setManagementTableData] = useState<any>([{}]) //테이블 데이터
|
||||
const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string }[]>([]) //테이블 헤더
|
||||
const [resultTableHeader, setResultTableHeader] = useState<{ id: string; title: string, mergeCell?: boolean }[]>([]) //테이블 헤더
|
||||
const [resultTableData, setResultTableData] = useState<any>([{}]) //테이블 데이터
|
||||
const [isLoading, setIsLoading] = useState(false) //로딩 상태
|
||||
|
||||
@@ -53,13 +56,12 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
// 관리 SHEET
|
||||
const managementHeader = jsonDataBySheet['관리SHEET']?.[7] || [];
|
||||
// 1번째~47번째, 74번째~80번째 데이터만 필터링
|
||||
const filteredManagementHeader = Object.entries(managementHeader)
|
||||
.filter(([key, value]) => {
|
||||
const match = key.match(/^__EMPTY_(\d+)$/);
|
||||
if (match) {
|
||||
const index = parseInt(match[1] ?? '', 10);
|
||||
return (index > 0 && index < 47) || (index > 73);
|
||||
return (0 < index && index < 47) || (73 < index);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
@@ -81,17 +83,20 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
// 운영관리현황 점검결과
|
||||
const resultHeader = jsonDataBySheet['운영관리현황 점검결과']?.[2] || [];
|
||||
const dynamicInspectionHeader = Object.entries(resultHeader)
|
||||
const dynamicResultHeader = Object.entries(resultHeader)
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
title: String(value).replace(/\r\n/g, '\n'),
|
||||
mergeCell: false,
|
||||
...(key === '__EMPTY_3' && { mergeCell: true }) // __EMPTY_3인 경우 mergeCell: true 추가
|
||||
}));
|
||||
dynamicInspectionHeader.splice(1, 0, {
|
||||
dynamicResultHeader.splice(1, 0, {
|
||||
id: "__EMPTY_2",
|
||||
title: ''
|
||||
title: '',
|
||||
mergeCell: false
|
||||
});
|
||||
setResultTableHeader(dynamicInspectionHeader);
|
||||
console.log('resultTableHeader', dynamicInspectionHeader);
|
||||
setResultTableHeader(dynamicResultHeader);
|
||||
console.log('resultTableHeader', dynamicResultHeader);
|
||||
|
||||
const resultData = Array.isArray(jsonDataBySheet['운영관리현황 점검결과'])
|
||||
? jsonDataBySheet['운영관리현황 점검결과'].slice(3)
|
||||
@@ -225,7 +230,7 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
)}
|
||||
{/* 운영관리현황 점검결과 탭 */}
|
||||
{activeTab === 'Inspection results' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="h-[79vh] w-full overflow-x-hidden overflow-y-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<UiTable
|
||||
@@ -238,6 +243,14 @@ const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
{/* <Table variant="secondary">
|
||||
<TableHeader className="font-bold">
|
||||
{HeadMakeFloodPre(resultTableHeader, [])}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{InputTableBody(resultTableData ?? [], resultTableHeader, setResultTableData, false)}
|
||||
</TableBody>
|
||||
</Table> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import { deleteData, postData, useCookieAuth } from '@/components/lib/api'
|
||||
import { searchData } from '@/components/lib/common/apiHelpers'
|
||||
import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils'
|
||||
import { formatDate, handleDownload } from '@/components/lib/utils'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable, UiModal } from '@common/business_components/ui'
|
||||
import ListDiv from '@common/business_components/ui/list_div'
|
||||
@@ -14,6 +14,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 TableModal from './tableModal';
|
||||
|
||||
|
||||
const Page = () => {
|
||||
@@ -30,7 +31,8 @@ const Page = () => {
|
||||
const [curPage, setCurPage] = useState(1)
|
||||
const [totalData, setTotalData] = useState(0) //총 데이터 수
|
||||
const [isLoading, setIsLoading] = useState(true) //로딩 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 등록 모달 상태
|
||||
const [isTableModalOpen, setIsTableModalOpen] = useState(false) // 테이블 모달 상태
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(false);
|
||||
|
||||
@@ -223,6 +225,7 @@ const Page = () => {
|
||||
setFileNm(fileName);
|
||||
setAtflId(id);
|
||||
setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn);
|
||||
setIsTableModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onPageChange={setCurPage}
|
||||
@@ -245,6 +248,21 @@ const Page = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* 엑셀 데이터 테이블 모달 */}
|
||||
<UiModal
|
||||
open={isTableModalOpen}
|
||||
onOpenChange={setIsTableModalOpen}
|
||||
bodyClass={`
|
||||
h-[100dvh]
|
||||
w-[90dvw]
|
||||
max-w-[100dvw]
|
||||
max-h-[100dvh]
|
||||
overflow-hidden
|
||||
z-[101]
|
||||
`}
|
||||
body={<TableModal fileNm={fileNm} atflId={atflId} />}
|
||||
useDirectRender={true}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
import { UITab, UiTable } from '@common/business_components/ui'
|
||||
import * as XLSX from 'xlsx';
|
||||
import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan';
|
||||
import { oldPpTable2HeadList, oldPpTable2NodeList, oldPpTable6HeadList, oldPpTable6NodeList } from './headList';
|
||||
|
||||
const TABS = [
|
||||
{ val: "1", text: "row data" },
|
||||
{ val: "2", text: "1.정밀조사" },
|
||||
{ val: "3", text: "2.정밀조사_정비사업연계" },
|
||||
{ val: "4", text: " 3.정비사업" },
|
||||
{ val: "5", text: "4.정비사업요약" },
|
||||
{ val: "6", text: "4.정비사업최종" },
|
||||
];
|
||||
|
||||
interface TableModalProps {
|
||||
fileNm: string;
|
||||
atflId: string;
|
||||
}
|
||||
|
||||
const TableModal: React.FC<TableModalProps> = ({ fileNm, atflId }) => {
|
||||
|
||||
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "1");
|
||||
const [tableHeader1, setTableHeader1] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader2, setTableHeader2] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader4, setTableHeader4] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableHeader5, setTableHeader5] = useState<{ id: string; title: string }[]>([])
|
||||
const [tableData1, setTableData1] = useState<any>([{}])
|
||||
const [tableData2, setTableData2] = useState<any>([{}])
|
||||
const [tableData3, setTableData3] = useState<any>([{}])
|
||||
const [tableData4, setTableData4] = useState<any>([{}])
|
||||
const [tableData5, setTableData5] = useState<any>([{}])
|
||||
const [tableData6, setTableData6] = useState<any>([{}])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
isChckFile();
|
||||
}, []);
|
||||
|
||||
// 파일 다운로드
|
||||
const isChckFile = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (fileNm === '' || fileNm === undefined) {
|
||||
alert('파일을 선택해주세요');
|
||||
setIsLoading(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch(`/attach-service/api/v1/download/${atflId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('파일을 가져오는 중 오류가 발생했습니다.');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
|
||||
const file = new File([blob], fileNm, {
|
||||
type: blob.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
|
||||
const targetSheets = ['row data', '1.정밀조사', '2.정밀조사_정비사업연계', ' 3.정비사업', '4.정비사업요약', '4.정비사업최종'];
|
||||
const jsonDataBySheet = await filePickedSelectedSheets(file, 0, targetSheets);
|
||||
|
||||
const header1 = jsonDataBySheet['row data']?.[1] || [];
|
||||
const header2 = jsonDataBySheet['1.정밀조사']?.[1] || [];
|
||||
const header4 = jsonDataBySheet[' 3.정비사업']?.[1] || [];
|
||||
const header5 = jsonDataBySheet['4.정비사업요약']?.[2] || [];
|
||||
|
||||
// row data
|
||||
const allEntries1 = Object.entries(header1);
|
||||
const filteredHeader1 = [
|
||||
allEntries1[0],
|
||||
allEntries1[1],
|
||||
allEntries1[2],
|
||||
allEntries1[3],
|
||||
allEntries1[5],
|
||||
allEntries1[6],
|
||||
allEntries1[7],
|
||||
allEntries1[8],
|
||||
allEntries1[9],
|
||||
allEntries1[10],
|
||||
allEntries1[11],
|
||||
allEntries1[12],
|
||||
allEntries1[13],
|
||||
allEntries1[14],
|
||||
allEntries1[15]
|
||||
].filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
filteredHeader1.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader1(filteredHeader1);
|
||||
|
||||
const data1 = Array.isArray(jsonDataBySheet['row data'])
|
||||
? jsonDataBySheet['row data'].slice(2)
|
||||
: [];
|
||||
const filteredData1 = filterEmptyRows(data1).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
__EMPTY_1: item.__EMPTY_1 || '-'
|
||||
}));
|
||||
setTableData1(filteredData1);
|
||||
|
||||
// 1.정밀조사
|
||||
const allEntries2 = Object.entries(header2);
|
||||
let filteredHeader2 = [
|
||||
allEntries2[0],
|
||||
allEntries2[1],
|
||||
allEntries2[2],
|
||||
allEntries2[3],
|
||||
allEntries2[5],
|
||||
allEntries2[6],
|
||||
allEntries2[7],
|
||||
allEntries2[8],
|
||||
allEntries2[9],
|
||||
allEntries2[10],
|
||||
allEntries2[11],
|
||||
allEntries2[12],
|
||||
allEntries2[13],
|
||||
allEntries2[14],
|
||||
allEntries2[15]
|
||||
].filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
filteredHeader2.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader2(filteredHeader2);
|
||||
|
||||
const data2 = Array.isArray(jsonDataBySheet['1.정밀조사'])
|
||||
? jsonDataBySheet['1.정밀조사'].slice(2)
|
||||
: [];
|
||||
const filteredData2 = filterEmptyRows(data2).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1
|
||||
}));
|
||||
setTableData2(filteredData2);
|
||||
|
||||
// 2.정밀조사_정비사업연계 (헤더는 import)
|
||||
const data3 = Array.isArray(jsonDataBySheet['2.정밀조사_정비사업연계'])
|
||||
? jsonDataBySheet['2.정밀조사_정비사업연계'].slice(5)
|
||||
: [];
|
||||
const filteredData3 = filterEmptyRows(data3).map((item: any, index: number) => {
|
||||
const newItem = { ...item };
|
||||
// 매핑 에러로 인해 특정 키를 "__EMPTY_0"으로 변경
|
||||
const targetKey = "*1차 출처: 공단자료\r\n*2차 출처: 정산서, 정밀조사보고서 등.\r\n (파랑) 2024년말 기준 실적 제출 자료. 준공 후 재확인 필요\r\n (노랑셀) 준공(또는 정산) 후에 재확인 필요";
|
||||
if (newItem[targetKey] !== undefined) {
|
||||
newItem["__EMPTY_0"] = newItem[targetKey];
|
||||
delete newItem[targetKey];
|
||||
}
|
||||
return {
|
||||
...newItem,
|
||||
__EMPTY_4: newItem?.__EMPTY_4 || '-',
|
||||
rowNumber: index + 1
|
||||
};
|
||||
});
|
||||
setTableData3(filteredData3);
|
||||
|
||||
// 3.정비사업
|
||||
const allEntries4 = Object.entries(header4);
|
||||
const filteredHeader4 = allEntries4.filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n')
|
||||
}));
|
||||
|
||||
filteredHeader4.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader4(filteredHeader4);
|
||||
|
||||
const data4 = Array.isArray(jsonDataBySheet[' 3.정비사업'])
|
||||
? jsonDataBySheet[' 3.정비사업'].slice(2)
|
||||
: [];
|
||||
const filteredData4 = filterEmptyRows(data4).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
__EMPTY_2: item?.__EMPTY_2 || '-',
|
||||
__EMPTY_11: item?.__EMPTY_11 || '-',
|
||||
}));
|
||||
setTableData4(filteredData4);
|
||||
|
||||
// 4.정비사업요약
|
||||
const allEntries5 = Object.entries(header5);
|
||||
const excludeKeys5 = [
|
||||
": \"정비사업\"sheet에서 사업코드 ctrl+c, Ctrl+v 한 다음 중복된 데이터 제거실시",
|
||||
"__EMPTY",
|
||||
"__EMPTY_8",
|
||||
"__EMPTY_15"
|
||||
];
|
||||
const filteredHeader5 = allEntries5
|
||||
.filter(([key]) => !excludeKeys5.includes(key))
|
||||
.filter((e): e is [string, any] => Boolean(e))
|
||||
.map(([key, value]) => ({
|
||||
id: key,
|
||||
title: String(value).replace(/\r\n/g, '\n'),
|
||||
}));
|
||||
filteredHeader5.splice(0, 0, {
|
||||
id: "rowNumber",
|
||||
title: '일련번호'
|
||||
});
|
||||
setTableHeader5(filteredHeader5);
|
||||
|
||||
const data5 = Array.isArray(jsonDataBySheet['4.정비사업요약'])
|
||||
? jsonDataBySheet['4.정비사업요약'].slice(3)
|
||||
: [];
|
||||
const filteredData5 =
|
||||
filterEmptyRows(data5).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
}));
|
||||
setTableData5(filteredData5);
|
||||
|
||||
// 4.정비사업최종 (헤더는 import)
|
||||
const data6 = Array.isArray(jsonDataBySheet['4.정비사업최종'])
|
||||
? jsonDataBySheet['4.정비사업최종'].slice(6)
|
||||
: [];
|
||||
const filteredData6 =
|
||||
filterEmptyRows(data6).map((item: any, index: number) => ({
|
||||
...item,
|
||||
rowNumber: index + 1,
|
||||
}));
|
||||
setTableData6(filteredData6);
|
||||
} catch (error) {
|
||||
console.error('파일 처리 중 오류 발생:', error);
|
||||
alert('파일 처리 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function filterEmptyRows(data: any[]) {
|
||||
return data
|
||||
.filter(row => {
|
||||
return Object.entries(row).some(([key, value]) => {
|
||||
const match = key.match(/^__EMPTY_(\d+)$/);
|
||||
if (!match || parseInt(match[1] ?? '', 10) < 2) return false;
|
||||
|
||||
return value !== undefined &&
|
||||
value !== null &&
|
||||
String(value).trim() !== '' &&
|
||||
String(value).trim() !== '-' &&
|
||||
String(value).trim() !== '';
|
||||
});
|
||||
})
|
||||
.map((row) => {
|
||||
const processedRow = { ...row };
|
||||
const keys = Object.keys(processedRow);
|
||||
|
||||
keys.forEach((key, keyIndex) => {
|
||||
// 마지막 속성(사업 근거)이 아닌 경우에만 빈 값을 '-'으로 변경
|
||||
if (keyIndex !== keys.length - 1) {
|
||||
const value = processedRow[key];
|
||||
if (value === undefined ||
|
||||
value === null ||
|
||||
value === '' ||
|
||||
String(value).trim() === '') {
|
||||
processedRow[key] = '-';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return processedRow;
|
||||
});
|
||||
};
|
||||
|
||||
// 특정 시트만 선택적으로 JSON으로 변환하는 함수
|
||||
function filePickedSelectedSheets(file: File, headerNum: number = 0, sheetNames: string[]): Promise<Record<string, any[]>> {
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onload = function (e) {
|
||||
const data = e.target?.result;
|
||||
if (!data) {
|
||||
console.error("Failed to read file: e.target.result is null");
|
||||
reject(new Error("Failed to read file"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Excel 파일 읽기
|
||||
const wb = XLSX.read(data, { type: 'binary', cellDates: true });
|
||||
const result: Record<string, any[]> = {};
|
||||
|
||||
// 지정된 시트 이름만 찾아서 처리
|
||||
sheetNames.forEach(targetSheetName => {
|
||||
// 대소문자 구분 없이 시트 이름 찾기
|
||||
const sheetName = wb.SheetNames.find(name =>
|
||||
name.toLowerCase() === targetSheetName.toLowerCase()
|
||||
);
|
||||
|
||||
if (sheetName) {
|
||||
const sheet = wb.Sheets[sheetName];
|
||||
if (sheet) {
|
||||
const jsonData = XLSX.utils.sheet_to_json(sheet, { range: headerNum, raw: false });
|
||||
result[targetSheetName] = jsonData;
|
||||
}
|
||||
} else {
|
||||
console.warn(`Sheet "${targetSheetName}" not found in workbook`);
|
||||
result[targetSheetName] = [];
|
||||
}
|
||||
});
|
||||
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
reader.onerror = function () {
|
||||
console.error("Error reading file");
|
||||
reject(new Error("Error reading file"));
|
||||
};
|
||||
|
||||
// 파일 읽기 시작
|
||||
reader.readAsBinaryString(file);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 탭 영역 */}
|
||||
<UITab
|
||||
defaultValue={activeTab}
|
||||
tabList={TABS.map((tab) => ({
|
||||
value: tab.val,
|
||||
label: tab.text
|
||||
}))}
|
||||
onValueChange={(value: string) => setActiveTab(value)}
|
||||
/>
|
||||
{/* row data 탭 */}
|
||||
{activeTab === '1' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[150%]">
|
||||
<UiTable
|
||||
headName={'row data'}
|
||||
tableData={tableData1}
|
||||
headlist={tableHeader1}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 1.정밀조사 탭 */}
|
||||
{activeTab === '2' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[150%]">
|
||||
<UiTable
|
||||
headName={'1.정밀조사'}
|
||||
tableData={tableData2}
|
||||
headlist={tableHeader2}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 2.정밀조사_정비사업연계 탭 */}
|
||||
{activeTab === '3' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[550%]">
|
||||
<UiTable
|
||||
headName={'2.정밀조사_정비사업연계'}
|
||||
tableData={tableData3}
|
||||
headlist={oldPpTable2HeadList}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={oldPpTable2NodeList} />
|
||||
</UiTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 3.정비사업 탭 */}
|
||||
{activeTab === '4' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[160%]">
|
||||
<UiTable
|
||||
headName={'3.정비사업'}
|
||||
tableData={tableData4}
|
||||
headlist={tableHeader4}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 4.정비사업요약 탭 */}
|
||||
{activeTab === '5' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[130%]">
|
||||
<UiTable
|
||||
headName={'4.정비사업요약'}
|
||||
tableData={tableData5}
|
||||
headlist={tableHeader5}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 4.정비사업최종 탭 */}
|
||||
{activeTab === '6' && (
|
||||
<div className="h-[79vh] w-full overflow-auto rounded-lg border">
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<div className="min-w-[200%]">
|
||||
<UiTable
|
||||
headName={'4.정비사업최종'}
|
||||
tableData={tableData6}
|
||||
headlist={oldPpTable6HeadList}
|
||||
pageSize={10}
|
||||
total={0}
|
||||
className={"overflow-x-auto overflow-y-auto"}
|
||||
cellClick={undefined}
|
||||
onPageChange={() => { }}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={oldPpTable6NodeList} />
|
||||
</UiTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default TableModal;
|
||||
@@ -27,3 +27,5 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
|
||||
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
|
||||
|
||||
@@ -16,6 +16,12 @@ 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_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
|
||||
|
||||
|
||||
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 # 박이정
|
||||
|
||||
@@ -16,6 +16,9 @@ NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BE
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api
|
||||
|
||||
|
||||
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
|
||||
NEXT_PUBLIC_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ruseBizMthdCd: SelectDataType[] = [
|
||||
{ val: "자체", text: "자체" },
|
||||
];
|
||||
|
||||
//공통 건축물 용도
|
||||
// 공통 건축물 용도
|
||||
export const bdstUsgCd: SelectDataType[] = [
|
||||
{ val: "-1", text: "확인불가" },
|
||||
{ val: "1", text: "운동장" },
|
||||
@@ -71,67 +71,67 @@ export const bdstUsgCd: SelectDataType[] = [
|
||||
{ val: "14", text: "골프장" },
|
||||
{ val: "15", text: "대규모점포" },
|
||||
{ val: "16", text: "기타" },
|
||||
{ text: "숙박업", val: "17" },
|
||||
{ text: "목욕장업", val: "18" },
|
||||
{ text: "공장", val: "19" },
|
||||
{ text: "발전시설", val: "20" },
|
||||
{ text: "관광단지개발사업", val: "21" },
|
||||
{ text: "도시개발사업", val: "22" },
|
||||
{ text: "산업단지개발사업", val: "23" },
|
||||
{ text: "택지개발사업", val: "24" },
|
||||
{ text: "대규모점포(대형마트)", val: "25" },
|
||||
{ text: "대규모점포(전문점)", val: "26" },
|
||||
{ text: "대규모점포(백화점)", val: "27" },
|
||||
{ text: "대규모점포(쇼핑센터)", val: "28" },
|
||||
{ text: "대규모점포(복합쇼핑몰)", val: "29" },
|
||||
{ text: "물류시설", val: "30" },
|
||||
{ text: "운수시설(여객자동차터미널)", val: "31" },
|
||||
{ text: "운수시설(철도시설)", val: "32" },
|
||||
{ text: "운수시설(공항시설)", val: "33" },
|
||||
{ text: "운수시설(항만시설)", val: "34" },
|
||||
{ text: "업무시설(공공업무시설)", val: "35" },
|
||||
{ text: "업무시설(금융업소)", val: "36" },
|
||||
{ text: "업무시설(사무소)", val: "37" },
|
||||
{ text: "업무시설(결혼상담소 등 소개업소)", val: "38" },
|
||||
{ text: "업무시설(출판사)", val: "39" },
|
||||
{ text: "업무시설(신문사)", val: "40" },
|
||||
{ text: "업무시설(오피스텔)", val: "41" },
|
||||
{ text: "교정시설", val: "42" },
|
||||
{ text: "방송국", val: "43" },
|
||||
{ text: "전신전화국", val: "44" },
|
||||
{ text: "기타", val: "45" },
|
||||
{ val: "17", text: "숙박업" },
|
||||
{ val: "18", text: "목욕장업" },
|
||||
{ val: "19", text: "공장" },
|
||||
{ val: "20", text: "발전시설" },
|
||||
{ val: "21", text: "관광단지개발사업" },
|
||||
{ val: "22", text: "도시개발사업" },
|
||||
{ val: "23", text: "산업단지개발사업" },
|
||||
{ val: "24", text: "택지개발사업" },
|
||||
{ val: "25", text: "대규모점포(대형마트)" },
|
||||
{ val: "26", text: "대규모점포(전문점)" },
|
||||
{ val: "27", text: "대규모점포(백화점)" },
|
||||
{ val: "28", text: "대규모점포(쇼핑센터)" },
|
||||
{ val: "29", text: "대규모점포(복합쇼핑몰)" },
|
||||
{ val: "30", text: "물류시설" },
|
||||
{ val: "31", text: "운수시설(여객자동차터미널)" },
|
||||
{ val: "32", text: "운수시설(철도시설)" },
|
||||
{ val: "33", text: "운수시설(공항시설)" },
|
||||
{ val: "34", text: "운수시설(항만시설)" },
|
||||
{ val: "35", text: "업무시설(공공업무시설)" },
|
||||
{ val: "36", text: "업무시설(금융업소)" },
|
||||
{ val: "37", text: "업무시설(사무소)" },
|
||||
{ val: "38", text: "업무시설(결혼상담소 등 소개업소)" },
|
||||
{ val: "39", text: "업무시설(출판사)" },
|
||||
{ val: "40", text: "업무시설(신문사)" },
|
||||
{ val: "41", text: "업무시설(오피스텔)" },
|
||||
{ val: "42", text: "교정시설" },
|
||||
{ val: "43", text: "방송국" },
|
||||
{ val: "44", text: "전신전화국" },
|
||||
{ val: "45", text: "기타" },
|
||||
];
|
||||
|
||||
export const rswtBdstType: SelectDataType[] = [
|
||||
{ text: "숙박업", val: "17" },
|
||||
{ text: "목욕장업", val: "18" },
|
||||
{ text: "공장", val: "19" },
|
||||
{ text: "발전시설", val: "20" },
|
||||
{ text: "관광단지개발사업", val: "21" },
|
||||
{ text: "도시개발사업", val: "22" },
|
||||
{ text: "산업단지개발사업", val: "23" },
|
||||
{ text: "택지개발사업", val: "24" },
|
||||
{ text: "대규모점포(대형마트)", val: "25" },
|
||||
{ text: "대규모점포(전문점)", val: "26" },
|
||||
{ text: "대규모점포(백화점)", val: "27" },
|
||||
{ text: "대규모점포(쇼핑센터)", val: "28" },
|
||||
{ text: "대규모점포(복합쇼핑몰)", val: "29" },
|
||||
{ text: "물류시설", val: "30" },
|
||||
{ text: "운수시설(여객자동차터미널)", val: "31" },
|
||||
{ text: "운수시설(철도시설)", val: "32" },
|
||||
{ text: "운수시설(공항시설)", val: "33" },
|
||||
{ text: "운수시설(항만시설)", val: "34" },
|
||||
{ text: "업무시설(공공업무시설)", val: "35" },
|
||||
{ text: "업무시설(금융업소)", val: "36" },
|
||||
{ text: "업무시설(사무소)", val: "37" },
|
||||
{ text: "업무시설(결혼상담소 등 소개업소)", val: "38" },
|
||||
{ text: "업무시설(출판사)", val: "39" },
|
||||
{ text: "업무시설(신문사)", val: "40" },
|
||||
{ text: "업무시설(오피스텔)", val: "41" },
|
||||
{ text: "교정시설", val: "42" },
|
||||
{ text: "방송국", val: "43" },
|
||||
{ text: "전신전화국", val: "44" },
|
||||
{ text: "기타", val: "45" },
|
||||
{ val: "17", text: "숙박업" },
|
||||
{ val: "18", text: "목욕장업" },
|
||||
{ val: "19", text: "공장" },
|
||||
{ val: "20", text: "발전시설" },
|
||||
{ val: "21", text: "관광단지개발사업" },
|
||||
{ val: "22", text: "도시개발사업" },
|
||||
{ val: "23", text: "산업단지개발사업" },
|
||||
{ val: "24", text: "택지개발사업" },
|
||||
{ val: "25", text: "대규모점포(대형마트)" },
|
||||
{ val: "26", text: "대규모점포(전문점)" },
|
||||
{ val: "27", text: "대규모점포(백화점)" },
|
||||
{ val: "28", text: "대규모점포(쇼핑센터)" },
|
||||
{ val: "29", text: "대규모점포(복합쇼핑몰)" },
|
||||
{ val: "30", text: "물류시설" },
|
||||
{ val: "31", text: "운수시설(여객자동차터미널)" },
|
||||
{ val: "32", text: "운수시설(철도시설)" },
|
||||
{ val: "33", text: "운수시설(공항시설)" },
|
||||
{ val: "34", text: "운수시설(항만시설)" },
|
||||
{ val: "35", text: "업무시설(공공업무시설)" },
|
||||
{ val: "36", text: "업무시설(금융업소)" },
|
||||
{ val: "37", text: "업무시설(사무소)" },
|
||||
{ val: "38", text: "업무시설(결혼상담소 등 소개업소)" },
|
||||
{ val: "39", text: "업무시설(출판사)" },
|
||||
{ val: "40", text: "업무시설(신문사)" },
|
||||
{ val: "41", text: "업무시설(오피스텔)" },
|
||||
{ val: "42", text: "교정시설" },
|
||||
{ val: "43", text: "방송국" },
|
||||
{ val: "44", text: "전신전화국" },
|
||||
{ val: "45", text: "기타" },
|
||||
];
|
||||
|
||||
export const orgwtKndCd = [
|
||||
@@ -186,6 +186,7 @@ export const pplLenght = [
|
||||
];
|
||||
|
||||
export const rainFcltCd = [
|
||||
{ val: "10", text: "확인불가" },
|
||||
{ val: "01", text: "지붕" },
|
||||
{ val: "02", text: "건물옥상" },
|
||||
{ val: "03", text: "골프장 연못" },
|
||||
@@ -195,7 +196,6 @@ export const rainFcltCd = [
|
||||
{ val: "07", text: "태양광패널" },
|
||||
{ val: "08", text: "연못" },
|
||||
{ val: "09", text: "기타" },
|
||||
{ val: "10", text: "확인불가" },
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ const hdtflObjSchema = z.object({
|
||||
.string()
|
||||
.min(1, { message: validationMessages.required("에너지원 종류") }),
|
||||
fclCpct: z
|
||||
.number()
|
||||
.positive({ message: validationMessages.required("시설 용량") }),
|
||||
.number().nullish(),
|
||||
//.positive({ message: validationMessages.required("시설 용량") }),
|
||||
plwtYn: z.enum(["Y", "N"]).default("Y"),
|
||||
orgwtWtritSpt: z
|
||||
.string()
|
||||
.min(1, { message: validationMessages.required("원수 취수 지점") }),
|
||||
.string().nullish(),
|
||||
// .min(1, { message: validationMessages.required("원수 취수 지점") }),
|
||||
orgwtOcrnProcs: z
|
||||
.string()
|
||||
.min(1, { message: validationMessages.required("원수 발생 공정") }),
|
||||
.string().nullish(),
|
||||
// .min(1, { message: validationMessages.required("원수 발생 공정") }),
|
||||
wtritEntrcTp: z
|
||||
.number()
|
||||
.max(100, { message: "온도는 100이상이 될 수 없습니다." })
|
||||
@@ -33,12 +33,12 @@ const hdtflObjSchema = z.object({
|
||||
amtgnr: z.number().default(0),
|
||||
prcsProcsYn: z.enum(["Y", "N"]).default("Y"),
|
||||
prcsFcltCpct: z
|
||||
.number()
|
||||
.positive({ message: validationMessages.required("처리시설 용량") }),
|
||||
.number().nullish(),
|
||||
// .positive({ message: validationMessages.required("처리시설 용량") }),
|
||||
fmYn: z.string().default(""),
|
||||
utztnCpct: z
|
||||
.number()
|
||||
.positive({ message: validationMessages.required("이용 용량") }),
|
||||
.number().nullish(),
|
||||
// .positive({ message: validationMessages.required("이용 용량") }),
|
||||
utztnUsg: z
|
||||
.string()
|
||||
.min(1, { message: validationMessages.required("이용 용도") }),
|
||||
|
||||
@@ -19,14 +19,9 @@ export const baseReuseBuildingSchema = z.object({
|
||||
instlCstSprtYn: z.string().default("Y"),
|
||||
instlCstSprtAmt: z.number().nullish(),
|
||||
grntchBdstCertYn: z.string().default("Y"),
|
||||
grntchBdstCertYr: z
|
||||
.string()
|
||||
.max(4, { message: validationMessages.yearFormat })
|
||||
.refine(
|
||||
(year) => year.length === 0 || /^\d{4}$/.test(year),
|
||||
{
|
||||
message: validationMessages.yearFormat,
|
||||
}
|
||||
grntchBdstCertYr: z.string().refine(
|
||||
(val) => val === "" || /^\d{4}$/.test(val),
|
||||
{ message: "연도는 빈값 또는 4자리 숫자여야 합니다." }
|
||||
),
|
||||
grntchBdstCertNo: z.string().default(""),
|
||||
grntchBdstCertGrd: z.string().default(""),
|
||||
@@ -59,7 +54,7 @@ export const chgBuildingSchema = z.object({
|
||||
export const htdflReuseBuildingSchema = z.object({
|
||||
bdstRuseFcltSn: z.number().nullish(),
|
||||
bdstInfoSn: z.number().nullish(),
|
||||
instlCmptnDt: z.coerce.date().default(() => new Date()),
|
||||
//instlCmptnDt: z.coerce.date().default(() => new Date()),
|
||||
instlCst: z.number().nullish(),
|
||||
operMngCd: z.string().optional().nullish(),
|
||||
anlOperCst: z.number().default(0),
|
||||
|
||||
@@ -14,7 +14,7 @@ const rswtrObjSchema = z.object({
|
||||
//wtrUsqty: z.number({ required_error: validationMessages.required("물사용량(m³)") }).positive({ message: validationMessages.required("물사용량(m³)") }),
|
||||
//oblgTrgtWtrUsqty: z.number().nullish().default(0),
|
||||
orgwtKndCd: z.string({ required_error: validationMessages.requiredSelect("원수 종류") }).min(1, { message: validationMessages.requiredSelect("원수 종류") }),
|
||||
filtrTrtmfctCpct: z.number({ required_error: validationMessages.required("처리시설 용량") }),
|
||||
filtrTrtmfctCpct: z.number().nullish(),
|
||||
filtrTrtmfctProcs: z.string({ required_error: validationMessages.required("주요처리공정") }).min(1, { message: validationMessages.required("주요처리공정") }),
|
||||
filtrTrtmfctUsgCd: z.string().default("").nullish(),
|
||||
rswtrUsqty: z.number().default(0),
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) {
|
||||
const { userSiSggCd } = code;
|
||||
const [useCityCd, setUserCityCd] = useState<SelectDataType[]>([]);
|
||||
const [useSggCd, setUserSggCd] = useState<SelectDataType[]>([]);
|
||||
|
||||
const [initDaddr, setInitDaddr] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (userSiSggCd.length === 2 && code.cityCd.length > 0) {
|
||||
@@ -56,6 +56,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) {
|
||||
form.setValue("ctpvCd", Number(city[0]?.val));
|
||||
form.setValue("sggCd", Number(sgg[0]?.val));
|
||||
}
|
||||
|
||||
}, [form, userSiSggCd, code.cityCd, code.sggCd]);
|
||||
|
||||
|
||||
@@ -67,9 +68,16 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) {
|
||||
return () => subscription?.unsubscribe();
|
||||
}, [form.form]);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("form ", form.watch());
|
||||
// }, [form.formState]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("form.formState ", form.watch("archArea"));
|
||||
}, [form.formState]);
|
||||
const bdstInfoSn = form.getValues("bdstInfoSn");
|
||||
if(bdstInfoSn&&bdstInfoSn>0)
|
||||
setInitDaddr(form.getValues("daddr"));
|
||||
}, [form.getValues("bdstInfoSn")]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -166,7 +174,7 @@ export default function BdstReg({ form, type }: { form: any; type?: string }) {
|
||||
/> */}
|
||||
<div>
|
||||
<SearchInputAddress
|
||||
initValue={undefined}
|
||||
initValue={initDaddr}
|
||||
setRtnData={(rtnData: addressType) => {
|
||||
console.log("rtnData", rtnData);
|
||||
field.onChange(rtnData.roadAddrPart1);
|
||||
|
||||
@@ -78,6 +78,10 @@ export default function HtdflRegForm({
|
||||
};
|
||||
|
||||
const cleanHtdflData = (raw: any): z.infer<typeof hdtflZod> => {
|
||||
const buildings =
|
||||
raw?.reuseBuilding && raw.reuseBuilding.length > 0
|
||||
? raw.reuseBuilding
|
||||
: [{}];
|
||||
return {
|
||||
...raw,
|
||||
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
|
||||
@@ -92,7 +96,7 @@ export default function HtdflRegForm({
|
||||
: new Date(item?.archChgPrmsnDt),
|
||||
bdstChgNm: item?.bdstChgNm ?? "", // null → ""
|
||||
})),
|
||||
reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({
|
||||
reuseBuilding:buildings.map((item: any) => ({
|
||||
...item,
|
||||
instlCmptnDt: new Date(item?.instlCmptnDt),
|
||||
htdflObj: {
|
||||
|
||||
@@ -84,13 +84,14 @@ export default function RainRegForm({
|
||||
typeof item?.grntchBdstCertYr === "string"
|
||||
? item?.grntchBdstCertYr
|
||||
: "", // null → ""
|
||||
bdstUsgCd: item?.bdstUsgCd ?? Number("-1"), // null → "-1"
|
||||
instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y"
|
||||
grntchBdstCertYn: item?.grntchBdstCertYn ?? "Y",
|
||||
grntchBdstCertYn: item?.grntchBdstCertYn ?? "D",
|
||||
rainyObj: {
|
||||
...item?.rainyObj,
|
||||
pntfColKnd:item.rainyObj?.pntfColKnd??'확인불가',
|
||||
pntfColKnd:item.rainyObj?.pntfColKnd??rainFcltCd[0]?.val??'',
|
||||
filtrTrtmfctPrs:item.rainyObj?.filtrTrtmfctPrs??'',
|
||||
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y",
|
||||
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "D",
|
||||
},
|
||||
})),
|
||||
};
|
||||
@@ -98,7 +99,7 @@ export default function RainRegForm({
|
||||
|
||||
useEffect(() => {
|
||||
if (grntchBdstCertYn === "N") {
|
||||
form.setValue("reuseBuilding.0.grntchBdstCertYr", "");
|
||||
form.setValue("reuseBuilding.0.grntchBdstCertYr", '');
|
||||
form.setValue("reuseBuilding.0.grntchBdstCertNo", "");
|
||||
form.setValue("reuseBuilding.0.grntchBdstCertGrd", "");
|
||||
} else if (instlCstSprtYn === "N") {
|
||||
@@ -298,7 +299,10 @@ export default function RainRegForm({
|
||||
selectClass={selectClass}
|
||||
label={""}
|
||||
selectArray={rainFcltCd}
|
||||
onSelectValue={readonly ? () => { } : field.onChange}
|
||||
onSelectValue={ (item) =>{
|
||||
if(readonly) return;
|
||||
field.onChange(item)
|
||||
} }
|
||||
value={field.value ?? rainFcltCd[0]?.val}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
@@ -521,8 +525,7 @@ export default function RainRegForm({
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.grntchBdstCertYr"
|
||||
render={({ field }) => {
|
||||
const isUnknown =
|
||||
field.value === null || field.value === "0000"; // 또는 Number(value) === -1
|
||||
const isUnknown = field.value === null || field.value === "0000"; // 또는 Number(value) === -1
|
||||
return (
|
||||
<FormUi label="인증년도">
|
||||
<Input
|
||||
@@ -535,7 +538,7 @@ export default function RainRegForm({
|
||||
field.onChange(""); // 초기화
|
||||
}
|
||||
}}
|
||||
value={isUnknown ? "확인불가" : (field.value ?? "")}
|
||||
value={isUnknown ? "확인불가" : (field.value ?? '9999')}
|
||||
onChange={(e) => {
|
||||
if (readonly) return;
|
||||
if (e.target.value === "확인불가") {
|
||||
|
||||
@@ -83,8 +83,10 @@ export default function RswtrRegForm({
|
||||
|
||||
const filtrTrtmfctCpct = form.watch('reuseBuilding.0.rswtrObj.filtrTrtmfctCpct')
|
||||
const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => {
|
||||
|
||||
console.log('raw ', raw)
|
||||
const buildings =
|
||||
raw?.reuseBuilding && raw.reuseBuilding.length > 0
|
||||
? raw.reuseBuilding
|
||||
: [{}];
|
||||
return {
|
||||
...raw,
|
||||
frstArchPrmsnDt: raw?.frstArchPrmsnDt
|
||||
@@ -93,14 +95,15 @@ export default function RswtrRegForm({
|
||||
archChgPrmsnDt: raw?.archChgPrmsnDt
|
||||
? new Date(raw?.archChgPrmsnDt)
|
||||
: null,
|
||||
reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({
|
||||
reuseBuilding:buildings.map((item: any) => ({
|
||||
...item,
|
||||
instlCmptnDt: item?.instlCmptnDt
|
||||
? new Date(item.instlCmptnDt)
|
||||
: new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화
|
||||
operMngCd: item?.operMngCd || "", // null → ""
|
||||
instlCstSprtYn: item?.instlCstSprtYn || code.yn[0]?.val, // null → "Y"
|
||||
grntchBdstCertYn: item?.grntchBdstCertYn || code.yn[0]?.val,
|
||||
instlCstSprtYn: item?.instlCstSprtYn || 'N', // null → "Y"
|
||||
grntchBdstCertYn: item?.grntchBdstCertYn ||'N',
|
||||
bdstUsgCd: item?.bdstUsgCd ?? Number("-1"), // null → "-1"
|
||||
rswtrObj: {
|
||||
...item?.rswtrObj,
|
||||
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn || code.yn[0]?.val,
|
||||
@@ -145,6 +148,9 @@ export default function RswtrRegForm({
|
||||
if (rswtZod) form.reset(cleanRswtData(rswtForm));
|
||||
}, [rswtForm, form]);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="!mx-[1.6rem] !p-0r">
|
||||
<Form {...form}>
|
||||
@@ -291,7 +297,7 @@ export default function RswtrRegForm({
|
||||
selectClass={selectClass}
|
||||
selectArray={code.orgwtKndCd}
|
||||
onSelectValue={readonly ? () => { } : field.onChange}
|
||||
value={field.value || code.orgwtKndCd[0]?.val}
|
||||
value={field.value ?? code.orgwtKndCd[0]?.val}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
</FormUi>
|
||||
|
||||
@@ -49,8 +49,8 @@ export function BdstReg<T extends MoType>({
|
||||
|
||||
const { userSiSggCd } = code;
|
||||
|
||||
const [bdstTable, setbdstTable] = useState<BdstTypeMap[T][]>();
|
||||
const [rainBdstForm, setRainBdstForm] = useState<BdstTypeMap[T]>(
|
||||
const [bdstTable, setBdstTable] = useState<BdstTypeMap[T][]>();
|
||||
const [bdstForm, setBdstForm] = useState<BdstTypeMap[T]>(
|
||||
{} as BdstTypeMap[T]
|
||||
);
|
||||
const [searchTxt, setSearchTxt] = useState<string>();
|
||||
@@ -83,7 +83,7 @@ export function BdstReg<T extends MoType>({
|
||||
archArea: archArea,
|
||||
};
|
||||
});
|
||||
setbdstTable(tableData(bdstData, { code }));
|
||||
setBdstTable(tableData(bdstData, { code }));
|
||||
}
|
||||
}, [bdst]);
|
||||
|
||||
@@ -118,13 +118,13 @@ export function BdstReg<T extends MoType>({
|
||||
if (val !== undefined && bdst?.bdstBase.length > 0) {
|
||||
const data: BdstTypeMap[T] = bdst?.bdstBase[val];
|
||||
if (data) {
|
||||
setRainBdstForm(data);
|
||||
setBdstForm(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setbdstTable([]); // 초기화 작업
|
||||
setBdstTable([]); // 초기화 작업
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
@@ -200,13 +200,13 @@ export function BdstReg<T extends MoType>({
|
||||
<div>
|
||||
{moType === "RNB" ? (
|
||||
<RainRegForm
|
||||
rainForm={rainBdstForm as customRainBdst}
|
||||
rainForm={bdstForm as customRainBdst}
|
||||
isModal={isModal}
|
||||
setIsModal={setIsModal}
|
||||
/>
|
||||
) : moType === "RST" ? (
|
||||
<RswtrRegForm
|
||||
rswtForm={rainBdstForm as customRswtBdst}
|
||||
rswtForm={bdstForm as customRswtBdst}
|
||||
isModal={isModal}
|
||||
setIsModal={(open?: boolean) => {
|
||||
if (setIsModal && open) {
|
||||
@@ -216,7 +216,7 @@ export function BdstReg<T extends MoType>({
|
||||
></RswtrRegForm>
|
||||
) : (
|
||||
<HtdflRegForm
|
||||
htdflForm={rainBdstForm as unknown as customHtdflBdst}
|
||||
htdflForm={bdstForm as unknown as customHtdflBdst}
|
||||
isModal={true}
|
||||
setIsModal={(open?: boolean) => {
|
||||
if (setIsModal && open) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/components/lib/func/commom";
|
||||
|
||||
import {
|
||||
DatePicker,
|
||||
// DatePicker,
|
||||
FormField,
|
||||
FormUi,
|
||||
SelectBox,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components/ui";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
|
||||
type RuseMngTableProps = {
|
||||
@@ -49,14 +50,17 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
form,
|
||||
formNm,
|
||||
indexBiz,
|
||||
readonly= false
|
||||
readonly = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<>
|
||||
<Table key={`${formNm}-table-${indexMng}-${indexBiz}`} variant="secondary"
|
||||
ref={ref}>
|
||||
<Table
|
||||
key={`${formNm}-table-${indexMng}-${indexBiz}`}
|
||||
variant="secondary"
|
||||
ref={ref}
|
||||
>
|
||||
<TableHeader className="font-bold">
|
||||
{upHeadList ? (
|
||||
headMakeprfmnc(headList, upHeadList)
|
||||
@@ -76,20 +80,20 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
|
||||
<TableBody>
|
||||
{tableData.map((item, i) => (
|
||||
<TableRow key={`${item.id }-${i}` } >
|
||||
<TableRow key={`${item.id}-${i}`}>
|
||||
{headList.map((head, j) => (
|
||||
<TableCell
|
||||
key={`${head.id}-${j}`}
|
||||
className="text-center whitespace-nowrap"
|
||||
onClick={() => { }}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={
|
||||
typeof indexMng === "number" &&
|
||||
indexMng >= 0 &&
|
||||
typeof indexBiz === "number" &&
|
||||
indexBiz >= 0
|
||||
indexMng >= 0 &&
|
||||
typeof indexBiz === "number" &&
|
||||
indexBiz >= 0
|
||||
? `items.${indexMng}.fcBizs.${indexBiz}.${formNm}.${i}.${head.id}`
|
||||
: typeof indexMng === "number" && indexMng >= 0
|
||||
? `items.${indexMng}.${formNm}.${i}.${head.id}`
|
||||
@@ -99,7 +103,7 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
<FormUi label="">
|
||||
{head.edit === true ? (
|
||||
<Input
|
||||
className={`${head.type === "number" ? "text-right" : "text-center"} !text-xl`}
|
||||
className={`${head.type === "number" ? "text-right" : "text-center"} text-gray-600`}
|
||||
type="text"
|
||||
onChange={(txt) => {
|
||||
if (head.type === "number") {
|
||||
@@ -113,8 +117,10 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
? formatCommaNumber(field.value)
|
||||
: field.value || ""
|
||||
}
|
||||
readOnly={readonly|| head.readonly ? true : false}
|
||||
maxLength={head.maxLength?head.maxLength:15}
|
||||
readOnly={
|
||||
readonly || head.readonly ? true : false
|
||||
}
|
||||
maxLength={head.maxLength ? head.maxLength : 15}
|
||||
/>
|
||||
) : head.type === "select" ? (
|
||||
<SelectBox
|
||||
@@ -134,23 +140,38 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
field.onChange(val);
|
||||
}
|
||||
}}
|
||||
readOnly={ readonly||head.readonly ? true : false}
|
||||
// Disabled prop removed as SelectBox does not support it
|
||||
readOnly={
|
||||
readonly || head.readonly ? true : false
|
||||
}
|
||||
// Disabled prop removed as SelectBox does not support it
|
||||
></SelectBox>
|
||||
) : head.type === "date" ? (
|
||||
<DatePicker
|
||||
mode="single"
|
||||
field={{
|
||||
value:
|
||||
typeof field.value === "string"
|
||||
? new Date(field.value)
|
||||
: field.value,
|
||||
onChange: (date: Date | undefined) => {
|
||||
console.log("date", date);
|
||||
field.onChange(date); // readonly 상태가 아닐 때만 값 변경
|
||||
},
|
||||
// <DatePicker
|
||||
// mode="single"
|
||||
// field={{
|
||||
// value:
|
||||
// typeof field.value === "string"
|
||||
// ? new Date(field.value)
|
||||
// : field.value,
|
||||
// onChange: (date: Date | undefined) => {
|
||||
// console.log("date", date);
|
||||
// field.onChange(date); // readonly 상태가 아닐 때만 값 변경
|
||||
// },
|
||||
// }}
|
||||
// selectClass={"w-full"}
|
||||
// readOnly={readonly||head.readonly ? true : false}
|
||||
// />
|
||||
<Input
|
||||
type="date"
|
||||
onChange={(e) => {
|
||||
// 브라우저에서 선택한 값은 "YYYY-MM-DD"
|
||||
const date = new Date(e.target.value); // e.g. "2025-06-01"
|
||||
field.onChange(date);
|
||||
}}
|
||||
selectClass={"w-full"}
|
||||
value={
|
||||
field.value? moment(field.value, "YYYYMMDD").format( "YYYY-MM-DD"): ""
|
||||
}
|
||||
className={"w-full text-gray-600"}
|
||||
readOnly={readonly||head.readonly ? true : false}
|
||||
/>
|
||||
) : head.type === "checkbox" ? (
|
||||
@@ -158,22 +179,19 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
|
||||
className=""
|
||||
checked={field.value || false}
|
||||
onCheckedChange={(checked) => {
|
||||
if(readonly || head.readonly) {
|
||||
if (readonly || head.readonly) {
|
||||
return;
|
||||
}else{
|
||||
} else {
|
||||
field.onChange(checked);
|
||||
}
|
||||
}}
|
||||
></Checkbox>
|
||||
) : (
|
||||
(
|
||||
|
||||
<span className="text-black">{
|
||||
head.type === "number"
|
||||
<span className="text-black">
|
||||
{head.type === "number"
|
||||
? formatCommaNumber(field.value)
|
||||
: field.value || ""
|
||||
}</span>
|
||||
)
|
||||
: field.value || ""}
|
||||
</span>
|
||||
)}
|
||||
</FormUi>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,8 @@ const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
|
||||
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || "";
|
||||
const OPERATION_API_URL = process.env.NEXT_PUBLIC_FO_SERVER_API_URL || "";
|
||||
const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || "";
|
||||
const AdressKey = process.env.NEXT_PUBLIC_WR_ADDRESS_KEY || "";
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
@@ -148,6 +150,24 @@ export function middleware(req: NextRequest) {
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
|
||||
if (pathname.startsWith("/bussiness-juso-service")) {
|
||||
console.log('AdressKey ', AdressKey)
|
||||
const params = new URLSearchParams({
|
||||
currentPage: "1", // 원하는 페이지
|
||||
countPerPage: "5", // 페이지당 결과 수
|
||||
keyword: searchParams.get("keyword") || "", // 검색어(주소)
|
||||
confmKey: AdressKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯
|
||||
resultType: "json", // 결과 형식 기본은 xml
|
||||
});
|
||||
const newUrl = `${BUSINESS_JUSO_API_URL}?${params.toString()}`;
|
||||
console.log(`[Middleware] Request to business.juso.go.kr`);
|
||||
console.log(req.url);
|
||||
console.log(`[Middleware] Rewriting from '/business.juso.go.kr' path to: ${newUrl}`);
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
@@ -161,6 +181,7 @@ export const config = {
|
||||
"/sewerage-service/:path*",
|
||||
"/reuse-service/:path*",
|
||||
"/operation-service/:path*",
|
||||
"/bussiness-juso-service/:path*",
|
||||
"/"
|
||||
],
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "./lib/chart";
|
||||
import type { ContentType } from "recharts/types/component/DefaultLegendContent";
|
||||
import type { set } from "date-fns";
|
||||
import React from "react";
|
||||
|
||||
// 툴크 커스텀 css
|
||||
/** 차트 기본 스타일 */
|
||||
@@ -126,6 +127,37 @@ export default function LineBarchart({
|
||||
monthTicks = getTicks(chartData);
|
||||
}
|
||||
|
||||
const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:string }> = ({ viewBox, fieldText, position }) => {
|
||||
const { x, y, width, height } = viewBox; // 축 영역
|
||||
const padding = 5; // 차트 바깥으로 띄우기
|
||||
|
||||
if('left' == position) {
|
||||
return (
|
||||
<text
|
||||
x={x - padding} // 맨 좌측 바깥
|
||||
y={y + height / 2} // 수직 중앙
|
||||
textAnchor="middle"
|
||||
transform={`rotate(-90, ${x - padding}, ${y + height / 2})`}
|
||||
className="text-[12px] font-bold"
|
||||
>
|
||||
{fieldText}
|
||||
</text>
|
||||
);
|
||||
}else{
|
||||
return (
|
||||
<text
|
||||
x={x + height / 2 } // 맨 좌측 바깥
|
||||
y={y - width + padding } // 수직 중앙
|
||||
textAnchor="middle"
|
||||
transform={`rotate(90, ${x}, ${y})`}
|
||||
className="text-[12px] font-bold"
|
||||
>
|
||||
{fieldText}
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={chartClassName}>
|
||||
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
|
||||
@@ -133,7 +165,7 @@ export default function LineBarchart({
|
||||
{chartTitle}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="h-[calc(100%-42px)] flex justify-center">
|
||||
<div className="h-[calc(100%-42px)] w-[calc(100%-40px)] ">
|
||||
<ChartContainer config={chartConfig ?? {}} className="h-full w-full">
|
||||
<ComposedChart
|
||||
data={chartData}
|
||||
@@ -151,18 +183,22 @@ export default function LineBarchart({
|
||||
yAxisId="left"
|
||||
className="text-[12px] font-bold"
|
||||
tickCount={6}
|
||||
orientation="left"
|
||||
domain={[0, Math.ceil(lineMaxValue * 1)]}
|
||||
label={{ value: yFieldText, angle: -90, position: 'insideLeft', offset: -8 }}
|
||||
tickFormatter={(value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
}
|
||||
orientation="left"
|
||||
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />}
|
||||
/>}
|
||||
{/* 오른쪽 Y축: barVal */}
|
||||
{yFieldText2 && <YAxis
|
||||
yAxisId="right"
|
||||
className="text-[12px] font-bold"
|
||||
tickCount={6}
|
||||
orientation="right"
|
||||
domain={[0, Math.ceil(barMaxValue * 1)]}
|
||||
label={{ value: yFieldText2, angle: -90, position: 'insideRight' }}
|
||||
orientation="right"
|
||||
tickFormatter={(value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
}
|
||||
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="right" />}
|
||||
reversed={true}
|
||||
/>}
|
||||
<Tooltip content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />} />
|
||||
|
||||
@@ -65,11 +65,11 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?:
|
||||
return (
|
||||
<div className="w-[180px] rounded bg-white p-2.5 shadow-[0px_2px_10px_rgba(0,0,0,0.2)]">
|
||||
<p className="text-[12px] font-bold m-3">{label}</p>
|
||||
{payload.map((item, index) => (
|
||||
{payload.filter(item => item.value != null && String(item.value) != 'NaN' && item.value != undefined).map((item, index) => (
|
||||
<div key={index} style={{ marginTop: "10px", display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
{/* 색상 박스 추가 (범례 색상과 동일) */}
|
||||
<div style={{ width: "10px", height: "10px", backgroundColor: item.color, borderRadius: "50%" }}></div>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value}`}</span>
|
||||
<span className="text-[11px] font-bold">{` ${item.name} : ${item.value.toString().replace(',','')}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -129,6 +129,37 @@ const CustomLegend = (props: LegendProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const CustomAxisText : React.FC<{ viewBox: any, fieldText: string, position:string }> = ({ viewBox, fieldText, position }) => {
|
||||
const { x, y, width, height } = viewBox; // 축 영역
|
||||
const padding = 5; // 차트 바깥으로 띄우기
|
||||
|
||||
if('left' == position) {
|
||||
return (
|
||||
<text
|
||||
x={x - padding} // 맨 좌측 바깥
|
||||
y={y + height / 2} // 수직 중앙
|
||||
textAnchor="middle"
|
||||
transform={`rotate(-90, ${x - padding}, ${y + height / 2})`}
|
||||
className="text-[12px] font-bold"
|
||||
>
|
||||
{fieldText}
|
||||
</text>
|
||||
);
|
||||
}else{
|
||||
return (
|
||||
<text
|
||||
x={x + height / 2 } // 맨 좌측 바깥
|
||||
y={y - width + padding } // 수직 중앙
|
||||
textAnchor="middle"
|
||||
transform={`rotate(90, ${x}, ${y})`}
|
||||
className="text-[12px] font-bold"
|
||||
>
|
||||
{fieldText}
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type DataType = {label: string; val: string};
|
||||
|
||||
type ChartDataType = {
|
||||
@@ -220,7 +251,10 @@ export default function Linechart({
|
||||
yAxisId="left"
|
||||
className="text-[12px] font-bold"
|
||||
orientation="left"
|
||||
label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} />
|
||||
tickFormatter={(value) =>
|
||||
typeof value === "number" ? value.toLocaleString() : value
|
||||
}
|
||||
label={(props) => <CustomAxisText {...props} fieldText={yFieldText} position="left" />}/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
{showLegend && <Legend content={CustomLegend as ContentType}/>}
|
||||
{Object.keys(chartData).map((key) => {
|
||||
@@ -233,8 +267,10 @@ export default function Linechart({
|
||||
type="linear"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
connectNulls={true}
|
||||
name={String(config?.label || "X축")}
|
||||
stroke={config?.color}
|
||||
stroke={config?.color?.includes('dash')?config?.color?.replace('dash',''):config?.color}
|
||||
strokeDasharray={config?.color?.includes("dash")?"4 4":undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -30,75 +30,6 @@ export const GisContext = createContext({
|
||||
checkedLayerIds: null,
|
||||
});
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
export const searchUpperPpls = async (fcltMngNo) => {
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
let data = null;
|
||||
if (res.ok) {
|
||||
try {
|
||||
// 응답이 비어있는지 확인
|
||||
const contentLength = res.headers.get("content-length");
|
||||
if (contentLength === "0")
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
|
||||
const text = await res.text();
|
||||
if (!text || text.trim() === "")
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
// JSON 파싱 시도
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error("JSON parsing error:", error);
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
const select = GisApp.Module.select;
|
||||
const gisMap = GisApp.Module.core?.map;
|
||||
let foundFeature = null;
|
||||
|
||||
data.forEach((item) => {
|
||||
const pplMngNo = item.ppl_mng_no.trim();
|
||||
|
||||
gisMap
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl")
|
||||
.forEach((layer) => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
for (const f of features) {
|
||||
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) select.selectMultiFeature(foundFeature, true);
|
||||
});
|
||||
|
||||
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
let geometry = foundFeature?.getGeometry();
|
||||
if (geometry) {
|
||||
let extent = geometry.getExtent();
|
||||
|
||||
gisMap.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 14,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function Gis({
|
||||
isOpen, //우측 상세 레이어 팝업
|
||||
@@ -121,7 +52,6 @@ function Gis({
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
height: height = "h-[calc(100vh-5rem)]",
|
||||
onOpenPopup = (data) => { },
|
||||
@@ -359,7 +289,6 @@ function Gis({
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying}
|
||||
targetLayerTypes={targetLayerTypes}
|
||||
orderedLayers={orderedLayers}
|
||||
checkInterval={checkInterval}
|
||||
onProgressChange={handleProgressChange}
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,6 @@ export default React.memo(function GisTree({
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
onProgressChange = () => { },
|
||||
}) {
|
||||
@@ -313,14 +312,15 @@ export default React.memo(function GisTree({
|
||||
|
||||
// orderedLayers 순서를 우선 적용하면서, 타입 필터에 맞는 타겟 노드만 선별
|
||||
const getTargetNodesInOrder = () => {
|
||||
const orderedNodes = [];
|
||||
orderedLayers.forEach((lyrSn) => {
|
||||
const node = nodes.find(
|
||||
(n) => n.id === lyrSn && layerTypes.includes(n.data?.lyrTyNm)
|
||||
);
|
||||
if (node) orderedNodes.push(node);
|
||||
const target = nodes.filter((n) => n.data?.actlLyrYn === 'N' && layerTypes.includes(n.data?.lyrTyNm));
|
||||
|
||||
// drawOrdr 값을 기준으로 오름차순 정렬
|
||||
target.sort((a, b) => {
|
||||
const aOrder = a.data?.drawOrdr || 0;
|
||||
const bOrder = b.data?.drawOrdr || 0;
|
||||
return aOrder - bOrder;
|
||||
});
|
||||
return orderedNodes;
|
||||
return target;
|
||||
};
|
||||
|
||||
const targetNodes = getTargetNodesInOrder();
|
||||
@@ -510,8 +510,8 @@ export default React.memo(function GisTree({
|
||||
{/* 사이드 패널 */}
|
||||
<div
|
||||
className={`absolute left-0 top-0 z-40 flex h-full w-[250px] flex-col overflow-hidden border-r border-gray-300 bg-white transition-all duration-300 ${visible
|
||||
? "translate-x-0 opacity-100"
|
||||
: "pointer-events-none -translate-x-full opacity-0"
|
||||
? "translate-x-0 opacity-100"
|
||||
: "pointer-events-none -translate-x-full opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -597,40 +597,63 @@ GisOlLayer.prototype = {
|
||||
const tileSize = 256; // 기본 타일 크기
|
||||
return Math.ceil(Math.log2(maxResolution / (cameraHeight / tileSize)));
|
||||
},
|
||||
ensureSearchLayer: function () {
|
||||
ensureSearchLayer: function (option) {
|
||||
let layer = this.searchLayer;
|
||||
|
||||
if (!layer) {
|
||||
layer = new VectorLayer({
|
||||
source: new VectorSource(),
|
||||
let source;
|
||||
|
||||
try {
|
||||
if (option.cmncNm == 'bbox') {
|
||||
source = this.getVectorSourceBbox(option);
|
||||
} else {
|
||||
source = this.getVectorSourceAll(option);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(option);
|
||||
}
|
||||
|
||||
layer = new VectorImageLayer({
|
||||
...option,
|
||||
id: 'search-layer',
|
||||
source: source,
|
||||
zIndex: 9999
|
||||
});
|
||||
layer.setStyle(this.gisStyle.getLayerStyleFunction(layer, option));
|
||||
|
||||
let map = GisApp.Module.core.map;
|
||||
map.addLayer(layer);
|
||||
|
||||
this.searchLayer = layer;
|
||||
}
|
||||
return layer;
|
||||
},
|
||||
clearSearchLayer: function () {
|
||||
const layer = this.ensureSearchLayer();
|
||||
clearSearchLayer: function (option) {
|
||||
const layer = this.ensureSearchLayer(option);
|
||||
layer.getSource().clear(true);
|
||||
|
||||
if (this.searchLayer) {
|
||||
GisApp.Module.core.map.removeLayer(this.searchLayer);
|
||||
this.searchLayer = null;
|
||||
}
|
||||
},
|
||||
showSearchResultsFromGeoJSON: function (lyrTyNm, featureId) {
|
||||
const layer = this.ensureSearchLayer();
|
||||
|
||||
// 이전 검색결과는 깨끗이
|
||||
const prev = layer.getSource();
|
||||
if (prev) prev.clear(true);
|
||||
|
||||
//레이어 타입 설정(point, line 등. 이걸 style에서 사용)
|
||||
const layerCode = GisApp.layerCode;
|
||||
const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm);
|
||||
// 이전 검색 레이어 완전히 제거
|
||||
this.clearSearchLayer(matched);
|
||||
// 새로운 검색 레이어 생성
|
||||
const layer = this.ensureSearchLayer(matched);
|
||||
layer.set("gobjTy", matched.gobjTy);
|
||||
|
||||
// 새 소스 만들기
|
||||
const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId);
|
||||
const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId, matched);
|
||||
|
||||
// 1) 로드 완료시 extent 맞추기
|
||||
const onLoadEnd = () => {
|
||||
if (Object.keys(source.nullGeometryFeatures_).length > 0) alert('해당 시설의 위치 정보가 없습니다.');
|
||||
|
||||
const feats = source.getFeatures();
|
||||
if (feats && feats.length > 0) {
|
||||
const select = GisApp.Module.select;
|
||||
@@ -665,12 +688,13 @@ GisOlLayer.prototype = {
|
||||
* @param {*} featureId
|
||||
* @returns
|
||||
*/
|
||||
getFeatureSourceFromGeoserver: function (lyrTyNm, featureId) {
|
||||
getFeatureSourceFromGeoserver: function (lyrTyNm, featureId, option) {
|
||||
|
||||
const typeName = this.storageNm + lyrTyNm;
|
||||
const srsName = GisApp.TO_PROJ;
|
||||
const baseUrl = GisApp.GEOSERVER_URL;
|
||||
const source = new VectorSource({
|
||||
...option,
|
||||
format: new GeoJSON(),
|
||||
url: () => {
|
||||
const params = new URLSearchParams({
|
||||
@@ -695,13 +719,14 @@ GisOlLayer.prototype = {
|
||||
* @param {Array|string} featureIds 피처 ID 배열 또는 단일 ID
|
||||
*/
|
||||
showMultipleSearchResultsFromGeoJSON: function (lyrTyNm, featureIds) {
|
||||
const layer = this.ensureSearchLayer();
|
||||
|
||||
const prev = layer.getSource();
|
||||
if (prev) prev.clear(true);
|
||||
|
||||
const layerCode = GisApp.layerCode;
|
||||
const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm);
|
||||
|
||||
// 이전 검색 레이어 완전히 제거
|
||||
this.clearSearchLayer(matched);
|
||||
// 새로운 검색 레이어 생성
|
||||
const layer = this.ensureSearchLayer(matched);
|
||||
layer.set("gobjTy", matched.gobjTy);
|
||||
|
||||
const source = this.getMultiFeatureSourceFromGeoserver(lyrTyNm, featureIds);
|
||||
|
||||
@@ -284,6 +284,7 @@ GisOlSelect.prototype = {
|
||||
selectedFeatures.forEach(feature => {
|
||||
selectedFeatures.remove(feature);
|
||||
});
|
||||
selectedFeatures.clear();
|
||||
},
|
||||
/**
|
||||
* SELECT 이벤트 활성화
|
||||
|
||||
@@ -412,10 +412,10 @@ export function SdmRegForm({
|
||||
// {...field}
|
||||
// />
|
||||
<SearchInputAddress
|
||||
initValue={undefined}
|
||||
initValue={updateList?.daddr || ""}
|
||||
readonly={readOnly ??false}
|
||||
setRtnData={(rtnData: addressType)=> {
|
||||
field.onChange(rtnData.roadAddrPart1);
|
||||
console.log("rtnData ", rtnData);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -1,85 +1,88 @@
|
||||
const addresssKey = 'devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM='; //인증키 만료일은 2025-11-23
|
||||
|
||||
// const Key = 'U01TX0FVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA'
|
||||
// const addresssKey = 'devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM='; //인증키 만료일은 2025-11-23
|
||||
|
||||
/**
|
||||
* 주소 API 응답 타입
|
||||
* https://business.juso.go.kr/addrlink/main.do 해당 사이트 참조
|
||||
*/
|
||||
export type AddressApiResponse ={
|
||||
results: {
|
||||
common: {
|
||||
errorMessage: string;
|
||||
countPerPage: string;
|
||||
totalCount: string;
|
||||
errorCode: string;
|
||||
currentPage: string;
|
||||
};
|
||||
juso: addressType[];
|
||||
results: {
|
||||
common: {
|
||||
errorMessage: string;
|
||||
countPerPage: string;
|
||||
totalCount: string;
|
||||
errorCode: string;
|
||||
currentPage: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type addressType ={
|
||||
detBdNmList: string;
|
||||
engAddr: string;
|
||||
rn: string;
|
||||
emdNm: string;
|
||||
zipNo: string;
|
||||
roadAddrPart2: string;
|
||||
emdNo: string;
|
||||
sggNm: string;
|
||||
jibunAddr: string;
|
||||
siNm: string;
|
||||
roadAddrPart1: string;
|
||||
bdNm: string;
|
||||
admCd: string;
|
||||
udrtYn: string;
|
||||
lnbrMnnm: string;
|
||||
roadAddr: string;
|
||||
lnbrSlno: string;
|
||||
buldMnnm: string;
|
||||
bdKdcd: string;
|
||||
liNm: string;
|
||||
rnMgtSn: string;
|
||||
mtYn: string;
|
||||
bdMgtSn: string;
|
||||
buldSlno: string;
|
||||
}
|
||||
juso: addressType[];
|
||||
};
|
||||
}
|
||||
|
||||
export type addressType ={
|
||||
detBdNmList: string;
|
||||
engAddr: string;
|
||||
rn: string;
|
||||
emdNm: string;
|
||||
zipNo: string;
|
||||
roadAddrPart2: string;
|
||||
emdNo: string;
|
||||
sggNm: string;
|
||||
jibunAddr: string;
|
||||
siNm: string;
|
||||
roadAddrPart1: string;
|
||||
bdNm: string;
|
||||
admCd: string;
|
||||
udrtYn: string;
|
||||
lnbrMnnm: string;
|
||||
roadAddr: string;
|
||||
lnbrSlno: string;
|
||||
buldMnnm: string;
|
||||
bdKdcd: string;
|
||||
liNm: string;
|
||||
rnMgtSn: string;
|
||||
mtYn: string;
|
||||
bdMgtSn: string;
|
||||
buldSlno: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 주소에 따른 검색
|
||||
* @param search : 검색어
|
||||
* @returns addressType[] 주소 배열
|
||||
*/
|
||||
/**
|
||||
* 주소에 따른 검색
|
||||
* @param search : 검색어
|
||||
* @returns addressType[] 주소 배열
|
||||
*/
|
||||
export const findAddress = async (search: string) => {
|
||||
if (!search) {
|
||||
return [];
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
currentPage: "1", // 원하는 페이지
|
||||
countPerPage: "5", // 페이지당 결과 수
|
||||
keyword: search, // 검색어(주소)
|
||||
confmKey: addresssKey, // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯
|
||||
resultType: "json", // 결과 형식 기본은 xml
|
||||
});
|
||||
const url = `https://business.juso.go.kr/addrlink/addrLinkApi.do?${params.toString()}`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.results && data.results.juso) {
|
||||
return data.results.juso as addressType[];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching address data:', error);
|
||||
return [];
|
||||
}
|
||||
if (!search) {
|
||||
return [];
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
currentPage: "1", // 원하는 페이지
|
||||
countPerPage: "5", // 페이지당 결과 수
|
||||
keyword: search, // 검색어(주소)
|
||||
confmKey: '', // 인증키 인터넷용 패쇄망용은 따로 발급 필요 즉 middleware에서 치환해서 사용해야할듯
|
||||
resultType: "json", // 결과 형식 기본은 xml
|
||||
});
|
||||
// const url = `https://business.juso.go.kr/addrlink/addrLinkApi.do?${params.toString()}`;
|
||||
const url = `/bussiness-juso-service/addrlink/addrLinkApi.do?${params.toString()}`;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.results && data.results.juso) {
|
||||
return data.results.juso as addressType[];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching address data:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ type thisProps = {
|
||||
* @returns setRtnData => 선택된 주소 데이터 반환
|
||||
*/
|
||||
export default function SearchInputAddress({
|
||||
initValue = undefined,
|
||||
initValue ,
|
||||
setRtnData = () => {},
|
||||
readonly=false
|
||||
}: thisProps) {
|
||||
@@ -24,6 +24,13 @@ export default function SearchInputAddress({
|
||||
const [searchList, setSearchList] = useState<addressType[]>([]);
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
|
||||
if(initValue !== textValue && initValue !== undefined && initValue !== null && initValue !== ''){
|
||||
setTextValue(initValue)
|
||||
}
|
||||
|
||||
},[initValue])
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
@@ -30,6 +30,7 @@ type UiTableProps = {
|
||||
boldArray?: number[];
|
||||
resetSelection?: boolean;
|
||||
index?: number;
|
||||
curPage?: number;
|
||||
headButton?: React.ReactNode | undefined;
|
||||
};
|
||||
|
||||
@@ -54,9 +55,10 @@ export function UiTable({
|
||||
boldArray = [],
|
||||
resetSelection = false,
|
||||
index = -1,
|
||||
curPage = 1,
|
||||
headButton = undefined,
|
||||
}: UiTableProps) {
|
||||
const [cur, setCur] = useState<number>(1);
|
||||
const [cur, setCur] = useState<number>(curPage);
|
||||
const [idx, setIdx] = useState<number>(index);
|
||||
const [headerHeight, setHeaderHeight] = useState<number>(0);
|
||||
const headerRef = useRef<HTMLTableSectionElement>(null);
|
||||
@@ -84,6 +86,10 @@ export function UiTable({
|
||||
setIdx(index);
|
||||
}, [index])
|
||||
|
||||
useEffect(() => {
|
||||
setCur(curPage);
|
||||
}, [curPage])
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCur(page);
|
||||
onPageChange(page);
|
||||
|
||||
Reference in New Issue
Block a user