Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -18,8 +18,8 @@ export interface Link {
|
||||
next_link_excn_dt: string;
|
||||
last_link_err_cd: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
link_batch_time: string;
|
||||
mdfr_id_id: string;
|
||||
link_cycle_mi: string;
|
||||
}
|
||||
|
||||
export interface LinkLog {
|
||||
@@ -48,8 +48,8 @@ export interface LinkInfo extends LinkFormData {
|
||||
next_link_excn_dt: string;
|
||||
last_link_err_cd: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
link_batch_time: number;
|
||||
mdfr_id: string;
|
||||
link_cycle_mi: number;
|
||||
}
|
||||
|
||||
export interface LinkStats {
|
||||
@@ -65,7 +65,7 @@ export interface LinkVrbl {
|
||||
link_vrbl_cd: string;
|
||||
link_vrbl_expln: string;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
mdfr_id: string;
|
||||
}
|
||||
|
||||
export interface LinkTable {
|
||||
@@ -94,7 +94,7 @@ export interface LinkTableColumns {
|
||||
pk_yn: string;
|
||||
col_seq: number;
|
||||
mdfcn_dt: string;
|
||||
mdfr: string;
|
||||
mdfr_id: string;
|
||||
}
|
||||
|
||||
export const linkFormSchema = z.object({
|
||||
@@ -118,8 +118,8 @@ export const linkFormSchema = z.object({
|
||||
next_link_excn_dt: z.string().max(40).nullable().optional(),
|
||||
last_link_err_cd: z.string().max(10).nullable().optional(),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
link_batch_time: z.coerce.number().nullable().optional().refine((val) => {
|
||||
mdfr_id: z.string().nullable().optional(),
|
||||
link_cycle_mi: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val >= 5;
|
||||
}, {
|
||||
@@ -135,7 +135,7 @@ export const linkVrblFormSchema = z.object({
|
||||
link_vrbl_cd: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
link_vrbl_vl: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
mdfr_id: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const LinkTableColumnsSchema = z.object({
|
||||
@@ -168,7 +168,7 @@ export const LinkTableColumnsSchema = z.object({
|
||||
message: "컬럼 매칭 순서는 0 이상이어야 합니다."
|
||||
}),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
mdfr_id: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
export const LinkTableSchema = z.object({
|
||||
@@ -182,7 +182,7 @@ export const LinkTableSchema = z.object({
|
||||
trsf_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블 한글명은 필수입니다.' }),
|
||||
trsf_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr: z.string().nullable().optional(),
|
||||
mdfr_id: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
interface LinkStatsListObject {
|
||||
|
||||
@@ -53,8 +53,8 @@ const columns = [
|
||||
header: '연계서버 IP',
|
||||
cell: (info) => (info.getValue() ? info.getValue() : '-'),
|
||||
}),
|
||||
columnHelper.accessor('link_batch_time', {
|
||||
id: 'link_batch_time',
|
||||
columnHelper.accessor('link_cycle_mi', {
|
||||
id: 'link_cycle_mi',
|
||||
header: '연계 주기',
|
||||
cell: (info) => convertMinutesToPeriod(Number(info.getValue())),
|
||||
}),
|
||||
|
||||
@@ -336,7 +336,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="link_batch_time"
|
||||
name="link_cycle_mi"
|
||||
render={({ field }) => {
|
||||
const timeParts = convertMinutesToPeriod(Number(field.value));
|
||||
return (
|
||||
@@ -434,9 +434,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
{form.formState.errors.link_batch_time && (
|
||||
{form.formState.errors.link_cycle_mi && (
|
||||
<p className="text-red-500 text-base pt-2">
|
||||
{form.formState.errors.link_batch_time.message}
|
||||
{form.formState.errors.link_cycle_mi.message}
|
||||
</p>
|
||||
)}
|
||||
</FormItem>
|
||||
@@ -520,7 +520,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
name="mdfr_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
|
||||
@@ -35,7 +35,7 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) {
|
||||
mutationFn: (data: LinkFormData) => LinkApi.updateLinkInfo({
|
||||
...data,
|
||||
link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null,
|
||||
mdfr: userUniqId
|
||||
mdfr_id: userUniqId
|
||||
}),
|
||||
onSuccess: () => {
|
||||
alert('수정되었습니다.');
|
||||
@@ -73,11 +73,11 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) {
|
||||
if (linkInfo) {
|
||||
let newData = { ...linkInfo };
|
||||
|
||||
if (linkInfo.mdfr) {
|
||||
if (linkInfo.mdfr_id) {
|
||||
const userName = await fetchUserInfo();
|
||||
newData = {
|
||||
...linkInfo,
|
||||
mdfr: userName,
|
||||
mdfr_id: userName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function RegistLinkPage() {
|
||||
link_id: '',
|
||||
link_pswd: '',
|
||||
srvc_usg: '',
|
||||
link_batch_time: 5,
|
||||
link_cycle_mi: 5,
|
||||
link_clsf_cd: 'none',
|
||||
},
|
||||
});
|
||||
@@ -44,8 +44,8 @@ function RegistLinkPage() {
|
||||
const insertData = {
|
||||
...data,
|
||||
link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null,
|
||||
next_link_excn_dt: dayjs().add(Number(data.link_batch_time), 'minute').format('YYYY-MM-DD HH:mm:ss'),
|
||||
mdfr: userUniqId,
|
||||
next_link_excn_dt: dayjs().add(Number(data.link_cycle_mi), 'minute').format('YYYY-MM-DD HH:mm:ss'),
|
||||
mdfr_id: userUniqId,
|
||||
};
|
||||
|
||||
insertLinkInfo(insertData);
|
||||
|
||||
@@ -292,7 +292,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
name="mdfr_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
|
||||
@@ -112,7 +112,7 @@ function LinkParamsPage() {
|
||||
await LinkApi.insertLinkVrbl({
|
||||
...data,
|
||||
link_sn: selectedLink.linkSn,
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
// 쿼리 무효화를 통한 최신 데이터 가져오기
|
||||
@@ -128,7 +128,7 @@ function LinkParamsPage() {
|
||||
mutationFn: async (data: LinkVrblFormData) => await LinkApi.updateLinkVrblInfo({
|
||||
...data,
|
||||
link_sn: selectedLink.linkSn,
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -229,11 +229,11 @@ function LinkParamsPage() {
|
||||
if (linkVrblInfo) {
|
||||
let newData = { ...linkVrblInfo };
|
||||
|
||||
if (linkVrblInfo.mdfr) {
|
||||
if (linkVrblInfo.mdfr_id) {
|
||||
const userName = await fetchUserInfo();
|
||||
newData = {
|
||||
...linkVrblInfo,
|
||||
mdfr: userName,
|
||||
mdfr_id: userName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink
|
||||
col_seq: null,
|
||||
pk_yn: 'N',
|
||||
mdfcn_dt: '',
|
||||
mdfr: '',
|
||||
mdfr_id: '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink
|
||||
<TableCell>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mdfr"
|
||||
name="mdfr_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
|
||||
@@ -78,7 +78,7 @@ function LinkTablePage() {
|
||||
const { mutateAsync: insertLinkTableCol } = useMutation({
|
||||
mutationFn: async (data: LinkTableColData) => await LinkApi.insertLinkTableCol({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
@@ -104,7 +104,7 @@ function LinkTablePage() {
|
||||
const { mutateAsync: updateLinkTableCol } = useMutation({
|
||||
mutationFn: async (data: LinkTableColData) => await LinkApi.updateLinkTableCol({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
}),
|
||||
onSuccess: async () => {
|
||||
if (selectedTableInfo) {
|
||||
@@ -280,11 +280,11 @@ function LinkTablePage() {
|
||||
if (colInfo) {
|
||||
let newData = { ...colInfo };
|
||||
|
||||
if (colInfo.mdfr) {
|
||||
if (colInfo.mdfr_id) {
|
||||
const userName = await fetchUserInfo();
|
||||
newData = {
|
||||
...colInfo,
|
||||
mdfr: userName,
|
||||
mdfr_id: userName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ function LinkTablePage() {
|
||||
if (await confirm(confirmMsg)) {
|
||||
insertLinkTableInfo({
|
||||
...data,
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "",
|
||||
});
|
||||
}
|
||||
@@ -330,7 +330,7 @@ function LinkTablePage() {
|
||||
updateLinkTableInfo({
|
||||
...data,
|
||||
tbl_sn: (regTblInfo.count > 0) ? regTblInfo.tbl_sn : "",
|
||||
mdfr: userUniqId,
|
||||
mdfr_id: userUniqId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,88 +1,62 @@
|
||||
'use client'
|
||||
import React, { useState,useEffect } from 'react'
|
||||
import Modal from '@/components/ui/modal';
|
||||
import React, { useState } from 'react'
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import { UiTable } from '@common/business_components/ui';
|
||||
import { UiModal } from '@common/business_components/ui';
|
||||
import { Button } from '@common/components/ui';
|
||||
import { useDcTree, flattenFolders, getFileDownload, addFileName, type FlattenedFolder} from '../../searchModal/searchModalReportApi';
|
||||
import ReportTab from '../../searchModal/reportTab';
|
||||
|
||||
// 하수처리시설
|
||||
const headList = [
|
||||
{ id: "title", title: "하수도정비 기본계획보고서" },
|
||||
];
|
||||
const modalHeadList = [
|
||||
{ id: "no", title: "번호" },
|
||||
{ id: "year", title: "승인연도" },
|
||||
{ id: "cad", title: '평면도' },
|
||||
{ id: "fileNm", title: "파일명" },
|
||||
{ id: "date", title: "입력시간" },
|
||||
]
|
||||
|
||||
const SwtrmtFclt = ({ gisObject }: { gisObject: any }) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [modalTitle, setModalTitle] = useState<string>('');
|
||||
const [dcDataOne, setDcDataOne] = useState<FlattenedFolder|undefined>({});
|
||||
const openModal = (title: string) => {
|
||||
setIsModalOpen(true);
|
||||
setModalTitle(title)
|
||||
};
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
const ctpvNm = gisObject?.ctpv_nm;
|
||||
//const reportOriginData = useDcTree(ctpvNm);
|
||||
const reportOriginData = useDcTree("강원특별자치도");
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
|
||||
if (!reportOriginData || reportOriginData.length === 0) {
|
||||
return;
|
||||
}
|
||||
const reportData = flattenFolders(reportOriginData);
|
||||
let filterReportData = [...reportData];
|
||||
if (gisObject && gisObject.sgg_nm !== undefined && gisObject.sgg_nm !== "") { // 시군구검색
|
||||
//const sggNm = gisObject?.sgg_nm;
|
||||
const sggNm = "강릉시";
|
||||
filterReportData = filterReportData.filter(item => item.sigungu === sggNm);
|
||||
}
|
||||
addFileName(filterReportData).then(data => {
|
||||
filterReportData = data.filter(item => item.filePath !== undefined && item.filePath !== "");
|
||||
if (filterReportData !== undefined && filterReportData.length > 0) {
|
||||
setDcDataOne(filterReportData[0]);
|
||||
}
|
||||
});
|
||||
console.log("filterReportData", filterReportData);
|
||||
};
|
||||
fetch();
|
||||
}, [reportOriginData]);
|
||||
|
||||
// 하수도정비기본계획 filedownload 핸들러
|
||||
const handleFileDownloadClick = () => {
|
||||
if (dcDataOne && dcDataOne !== undefined && dcDataOne.fileSn !== undefined && dcDataOne.fileSn !== ""
|
||||
&& dcDataOne.filePath !== undefined && dcDataOne.filePath !== "" && dcDataOne.fileName !== undefined && dcDataOne.fileName !== "" ) {
|
||||
console.log("dcDataOne", dcDataOne);
|
||||
getFileDownload(String(dcDataOne.fileSn), String(dcDataOne.fileName), String(dcDataOne.filePath));
|
||||
}
|
||||
};
|
||||
|
||||
//cad: <Button onClick={() => openModal('하수도대장 평면도')}>평면도</Button>,
|
||||
const ctpvNm = gisObject?.ctpv_nm;
|
||||
const sggNm = gisObject?.sgg_nm;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VerticalTable
|
||||
children={{
|
||||
title: <Button className="gray" onClick={() => handleFileDownloadClick()}>다운로드</Button>
|
||||
}} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}></VerticalTable>
|
||||
{/* <Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
|
||||
<div className='grid grid-col-2'>
|
||||
<UiTable headName={modalTitle} tableData={[]} headlist={modalHeadList} pageSize={20} total={0} cellClick={undefined} onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
}></UiTable>
|
||||
</div>
|
||||
</Modal> */}
|
||||
</div>
|
||||
<VerticalTable
|
||||
children={{
|
||||
title: <Button className="gray" onClick={() => setIsModalOpen(true)}>조회</Button>
|
||||
}}
|
||||
headName='디지털 자료관 연계정보'
|
||||
tableData={[]}
|
||||
headlist={headList}
|
||||
pageSize={20}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
isEditable={true}
|
||||
onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
/>
|
||||
<UiModal
|
||||
open={isModalOpen}
|
||||
onOpenChange={setIsModalOpen}
|
||||
headerTitle="하수도정비 기본계획보고서"
|
||||
headerTitleClass="text-3xl font-semibold text-gray-700"
|
||||
bodyClass={`
|
||||
h-[100vh]
|
||||
w-[60vw]
|
||||
max-w-[60vw]
|
||||
max-h-[100vh]
|
||||
overflow-hidden
|
||||
z-[101]
|
||||
`}
|
||||
body={
|
||||
<ReportTab
|
||||
open={isModalOpen}
|
||||
isSearch={false}
|
||||
ctpvNm={ctpvNm}
|
||||
sggNm={sggNm}
|
||||
/>
|
||||
}
|
||||
useDirectRender={true}
|
||||
/>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from "@/components/ui/modal";
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { useGisContext } from "@common/business_components/ui/map/Gis";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
|
||||
import { useEffect, useRef, useState, useMemo } from "react";
|
||||
import { getLayerContentComponent } from "../common";
|
||||
import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table";
|
||||
import InfilChart from './chart/infilChart';
|
||||
@@ -61,9 +61,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
let featureLayer: { get: (key: string) => any }; // 피처가 속한 레이어를 찾기 위한 변수
|
||||
layers.some((layer: any) => {
|
||||
const source = layer.getSource();
|
||||
// console.log('실행됨3 source', source);
|
||||
// console.log('실행됨3 featureData', featureData);
|
||||
// console.log('실행됨3 featureData.fid', featureData.fid);
|
||||
// if (source && featureData && featureData.fid) {
|
||||
// featureLayer = layer; // 피처가 속한 레이어 찾기
|
||||
// }
|
||||
@@ -131,8 +128,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
setIsClicked(false);
|
||||
console.log('SearchModal data', featureData);
|
||||
const lyrTyNm = featureData.lyrTypeNm;
|
||||
console.log('실행됨4 SearchModal 클릭 이벤트', lyrTyNm);
|
||||
console.log('실행됨4 SearchModal 클릭 이벤트 featureData', featureData);
|
||||
if (featureData.lyrTypeNm === 'tb_ms_swage_ppl') {
|
||||
result = '하수관로';
|
||||
setHeadlist(MsSwagePplCol2)
|
||||
@@ -187,7 +182,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
|
||||
setLayerNm(selectedLayerName); // 상태 업데이트
|
||||
|
||||
// console.log('gis data', featureData.values_);
|
||||
let data;
|
||||
if (featureData['ol_uid']) data = featureData.values_; // Gis 레이어 클릭 이벤트일 때
|
||||
else data = featureData; // SearchModal 클릭 이벤트일 때
|
||||
@@ -271,11 +265,12 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
|
||||
// 자체입력정보 API 엔드포인트 선택
|
||||
let apiEndpoint = '';
|
||||
const getFeatureId = featureId.toString().split('.').pop() || null;
|
||||
if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${getFeatureId}`;
|
||||
else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${getFeatureId}`;
|
||||
else if (selectedLayerName === '오수펌프' || selectedLayerName === '빗물펌프') apiEndpoint = `v1/fcltpump/${getFeatureId}`;
|
||||
else if (selectedLayerName === '하수맨홀') apiEndpoint = `v1/fcltmhl/${getFeatureId}`;
|
||||
const fcltMngNo = featureData?.values_?.fclt_mng_no || null;
|
||||
const mngInstCd = featureData?.values_?.mng_inst_cd || null;
|
||||
if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${fcltMngNo}/${mngInstCd}`;
|
||||
else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${fcltMngNo}/${mngInstCd}`;
|
||||
else if (selectedLayerName === '하수펌프장') apiEndpoint = `v1/fcltpump/${fcltMngNo}/${mngInstCd}`;
|
||||
else if (selectedLayerName === '하수맨홀') apiEndpoint = `v1/fcltmhl/${fcltMngNo}/${mngInstCd}`;
|
||||
|
||||
// API에서 상세 데이터 가져오기
|
||||
if (apiEndpoint) {
|
||||
@@ -354,6 +349,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
else if (layerNm === '하수맨홀') apiEndpoint = 'v1/fcltmhl';
|
||||
|
||||
bodyData['fcltMngNo'] = featureData.values_.fclt_mng_no.toString() || null;
|
||||
bodyData['mngInstCd'] = featureData.values_.mng_inst_cd.toString() || null;
|
||||
// 빈 키 제거
|
||||
if (bodyData[""] !== undefined) delete bodyData[""];
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { VerticalTable } from '@/components/ui/verticalTable';
|
||||
import React, { useState, useEffect } from 'react'
|
||||
// 빗물펌프 자체 입력 정보
|
||||
// 빗물펌프장 자체 입력 정보
|
||||
const exclnInputColumn = [
|
||||
{ id: "swtrmtZoneCd", title: "배수구역", type: "input" },
|
||||
{ id: "mltvDtdvNm", title: "배수분구", type: "input" },
|
||||
{ id: "dsgnFrqnc", title: "설계빈도(년)", type: "input" },
|
||||
{ id: "designFrqYr", title: "설계빈도(년)", type: "input" },
|
||||
]
|
||||
// 오수펌프 자체 입력 정보
|
||||
// 오수펌프장 자체 입력 정보
|
||||
const drtwInputColumn = [
|
||||
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
|
||||
{ id: "mltv_dtdv_nm", title: "처리분구", type: "input" },
|
||||
{ id: "swtrmtZoneCd", title: "처리구역", type: "input" },
|
||||
{ id: "mltvDtdvNm", title: "처리분구", type: "input" },
|
||||
]
|
||||
|
||||
// 하수펌프장
|
||||
|
||||
@@ -7,7 +7,7 @@ import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import SearchModal from "./searchModal/searchModal";
|
||||
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
import { WaterDepth } from "./waterDepth/waterDepth";
|
||||
import { WaterDepth, WATER_DEPTH_LAYER_TYPES, ORDERED_LAYERS } from "./waterDepth/waterDepth";
|
||||
|
||||
// 타입 정의 추가
|
||||
export type LyrStngProps = {
|
||||
@@ -40,6 +40,8 @@ const Map = () => {
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -118,6 +120,21 @@ const Map = () => {
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
// WaterDepth 표시 여부를 결정하는 함수
|
||||
const handleWaterDepthVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => {
|
||||
const waterDepthLayers = checkedNodesData.filter(node =>
|
||||
WATER_DEPTH_LAYER_TYPES.includes(node.data?.lyrTyNm)
|
||||
);
|
||||
|
||||
const hasWaterDepthLayer = waterDepthLayers.length > 0;
|
||||
setShowWaterDepth(hasWaterDepthLayer);
|
||||
}, []);
|
||||
|
||||
// 체크된 레이어 변경 처리 함수
|
||||
const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => {
|
||||
handleWaterDepthVisibility(checkedIds, checkedNodesData);
|
||||
}, [handleWaterDepthVisibility]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
@@ -135,10 +152,14 @@ const Map = () => {
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={undefined}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
searchBar={SearchBar}
|
||||
// waterDepth={WaterDepth}
|
||||
waterDepth={undefined}
|
||||
waterDepth={showWaterDepth ? WaterDepth : undefined}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying as any}
|
||||
targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any}
|
||||
orderedLayers={ORDERED_LAYERS as any}
|
||||
checkInterval={2500}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Button, Label } from '@common/components';
|
||||
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
|
||||
import { SelectBox, UiTable } from '@common/business_components/ui';
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar';
|
||||
|
||||
import { addFileName, flattenFolders, getFileDownload, useCityCd, useDcTree, useSggCd, reportGubuns } from './searchModalReportApi';
|
||||
import { type SelectDataType } from '../commonStore';
|
||||
import { addFileName, flattenFolders, getFileDownload, getDcTree, useDcTree, reportGubuns } from './searchModalReportApi';
|
||||
import useStore from '../../../store/useStore';
|
||||
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
|
||||
|
||||
|
||||
// 하수도정비 기본계획 보고서
|
||||
@@ -15,18 +15,19 @@ export const reportTableHeadlist = [
|
||||
{ id: "sido", title: "시도" },
|
||||
{ id: "sigungu", title: "시군구" },
|
||||
{ id: "approvalYear", title: "승인년도" },
|
||||
{ id: "plantZoneGugun", title: "구분" },
|
||||
{ id: "plantZoneGubun", title: "구분" },
|
||||
{ id: "reportGubun", title: "보고서구분" },
|
||||
{ id: "fileName", title: "파일명" },
|
||||
{ id: "regDate", title: "등록일자" },
|
||||
];
|
||||
|
||||
|
||||
interface BasicTabProps {
|
||||
interface ReportTabProps {
|
||||
open?: boolean;
|
||||
isSearch?: boolean;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
}
|
||||
|
||||
const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
const ReportTab: React.FC<ReportTabProps> = ({ open, isSearch = true, ctpvNm, sggNm }) => {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(false);
|
||||
@@ -36,24 +37,20 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
const [tableCount, setTableCount] = useState(0);
|
||||
const [curPage, setCurPage] = useState(1);
|
||||
const [yearFilter, setYearFilter] = useState({ start: "", end: "" });
|
||||
const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report
|
||||
const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report
|
||||
const [filteredSiguns, setFilteredSiguns] = useState<SelectDataType[]>([{ val: '0', text: '전체', sido: '0' }]);
|
||||
const [gubun, setGubun] = useState(""); // 하수정비기본계획 report
|
||||
const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report
|
||||
|
||||
const [ctpvCd, setCtpvCd] = useState(0);
|
||||
const [sggCd, setSggCd] = useState(0);
|
||||
const [reportGubun, setReportGubun] = useState("0");
|
||||
// const [gubun, setGubun] = useState("");
|
||||
|
||||
const { ctpv, sgg } = useStore();
|
||||
|
||||
// 하수정비기본계획 report
|
||||
const sidos = useCityCd();
|
||||
const siguns = useSggCd();
|
||||
const reportOriginData = useDcTree(""); //:Folder[]
|
||||
|
||||
const dynamicHeadlist: { id: string, title: string }[] = reportTableHeadlist;
|
||||
|
||||
useEffect(() => {
|
||||
// filterParams 초기화
|
||||
setCtpvCd('0');
|
||||
setSigungu('0');
|
||||
setCtpvCd(0);
|
||||
setSggCd(0);
|
||||
setReportGubun('0');
|
||||
setYearFilter({ start: "", end: "" });
|
||||
setShowTable(false);
|
||||
@@ -64,27 +61,36 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
setIsResetSelection(true); // 선택 초기화
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
let newCtpvCd = ctpv.find((n) => n.name === ctpvNm)?.id || 0;
|
||||
let newSggCd = sgg.find((n) => n.name === sggNm)?.id || 0;
|
||||
|
||||
setCtpvCd(newCtpvCd);
|
||||
setSggCd(newSggCd);
|
||||
}, [ctpvNm, sggNm]);
|
||||
|
||||
// ctpvCd, sggCd가 업데이트된 후에 fetchFilteredData 실행
|
||||
useEffect(() => {
|
||||
if (!isSearch && ctpvCd && sggCd) {
|
||||
fetchFilteredData();
|
||||
}
|
||||
}, [reportOriginData, ctpvCd, sggCd, isSearch]);
|
||||
|
||||
// 페이지 변경 시마다 데이터 fetch
|
||||
useEffect(() => {
|
||||
// showTable이 true일 때만 fetch (최초 렌더 방지)
|
||||
setIsResetPage(false);
|
||||
setIsResetSelection(true); // 선택 초기화
|
||||
if (showTable) fetchFilteredData();
|
||||
}, [curPage]);
|
||||
|
||||
const handleCtpvCdChange = (val: string | number) => { // 하수도정비기본계획.시도
|
||||
const sidoOption = sidos.find((n) => n.val === val);
|
||||
setCtpvCd(String(val));
|
||||
if (val !== '0') {
|
||||
const filterSiguns = siguns.filter(item => item.sido === sidoOption?.text);
|
||||
filterSiguns.unshift({ val: '0', text: '전체', sido: '0' });
|
||||
setFilteredSiguns(filterSiguns);
|
||||
}
|
||||
setSigungu("0");
|
||||
setGubun("");
|
||||
setYearFilter({ start: "", end: "", });
|
||||
}
|
||||
setIsResetSelection(true);
|
||||
|
||||
if (showTable) fetchFilteredData();
|
||||
|
||||
setIsResetSelection(false);
|
||||
}, [curPage]);
|
||||
|
||||
const handleCtpvCdChange = (sidoId: number) => {
|
||||
setCtpvCd(sidoId);
|
||||
setSggCd(0);
|
||||
}
|
||||
|
||||
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
|
||||
const handleSearch = () => {
|
||||
@@ -96,7 +102,7 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
};
|
||||
|
||||
// 하수도정비기본계획 테이블 cellClick 핸들러
|
||||
const handleReportCellClick = (index: number | undefined ) => {
|
||||
const handleReportCellClick = (index: number | undefined) => {
|
||||
console.log(index);
|
||||
if (typeof index === "number" && tableData[index]) {
|
||||
const reportData = tableData[index];
|
||||
@@ -107,23 +113,30 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 검색 API 호출 함수
|
||||
const fetchFilteredData = async () => {
|
||||
setIsLoading(true); // 로딩 시작
|
||||
setIsResetSelection(true); // 선택 초기화
|
||||
|
||||
|
||||
try {
|
||||
//const reportOriginData = await getDcTree('경기도');
|
||||
const reportData = flattenFolders(reportOriginData);
|
||||
let filterReportData = reportData;
|
||||
if (ctpvCd !== "0") { // 시도검색
|
||||
const sidoOption = sidos.find((n) => n.val === ctpvCd);
|
||||
filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text);
|
||||
console.log("필터링 전 보고서 데이터:", filterReportData);
|
||||
console.log('ctpv, sgg, ctpvCd, sggCd', ctpv, sgg, ctpvCd, sggCd);
|
||||
|
||||
if (ctpvCd) { // 시도검색
|
||||
console.log('시도 실행되나');
|
||||
const sidoOption = ctpv.find((n) => String(n.id) === String(ctpvCd));
|
||||
console.log('sidoOption', sidoOption)
|
||||
filterReportData = filterReportData.filter(item => item.sido === sidoOption?.name);
|
||||
console.log('시도 필터링 후:', filterReportData);
|
||||
}
|
||||
if (sigungu !== "0") { // 시군구검색
|
||||
const sigunguOption = filteredSiguns.find((n) => n.val === sigungu);
|
||||
filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text);
|
||||
if (sggCd) { // 시군구검색
|
||||
console.log('시군구 실행되나');
|
||||
const sigunguOption = sgg.find((n) => String(n.id) === String(sggCd));
|
||||
filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.name);
|
||||
console.log('시군구 필터링 후:', filterReportData);
|
||||
}
|
||||
if (reportGubun !== "0") { // 보고서구분검색
|
||||
const reportGubunOption = reportGubuns.find((n) => n.val === reportGubun);
|
||||
@@ -139,14 +152,16 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
return (item.approvalYear) ? item.approvalYear >= yearFilter.start + "년" && item.approvalYear <= yearFilter.end + "년" : null;
|
||||
});
|
||||
}
|
||||
if (gubun !== "") { // 구분검색
|
||||
filterReportData = filterReportData.filter(item => (item.plantZoneGugun) ? String(item.plantZoneGugun).includes(String(gubun)) : null);
|
||||
}
|
||||
// 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);
|
||||
|
||||
filterReportData = filterReportData.slice((curPage - 1) * 10, curPage * 10); // paging
|
||||
filterReportData = filterReportData?.map((item, index) => ({ ...item, no: (curPage - 1) * 10 + index + 1 }));
|
||||
//const fileList = await getDcFile("CLS20250530000041");
|
||||
|
||||
addFileName(filterReportData).then(data => {
|
||||
filterReportData = data;
|
||||
setTableData(filterReportData || []);
|
||||
@@ -159,69 +174,70 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
|
||||
setTableData([]);
|
||||
} finally {
|
||||
setIsLoading(false); // 로딩 종료
|
||||
setIsResetSelection(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]">
|
||||
<div className="flex items-center mb-3">
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
|
||||
<SelectBox className="w-60 mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="" selectArray={sidos}
|
||||
value={ctpvCd} onSelectValue={handleCtpvCdChange}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
|
||||
<SelectBox className="w-48"
|
||||
selectClass="w-48 rounded-lg bg-white truncate"
|
||||
label="" selectArray={filteredSiguns}
|
||||
value={sigungu} onSelectValue={val => setSigungu(String(val))}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mb-6">
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">보고서 구분</Label>
|
||||
<SelectBox className="w-44 mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="" selectArray={[...reportGubuns]}
|
||||
value={reportGubun} onSelectValue={val => setReportGubun(String(val))}
|
||||
selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2'
|
||||
/>
|
||||
{/* 승인년도 필터 */}
|
||||
<YearRangeSelectBox start={yearFilter.start} end={yearFilter.end}
|
||||
onChange={range => setYearFilter({ ...yearFilter, start: range.start, end: range.end })}
|
||||
startLabel="승인년도" endLabel=""
|
||||
/>
|
||||
{/* 조회 버튼 */}
|
||||
<div className="flex-1 flex justify-end">
|
||||
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
<div className={`relative flex flex-col border rounded-lg p-5 w-full ${isSearch ? "h-[10vh]" : "h-[6vh]"}`}>
|
||||
{isSearch && (
|
||||
<>
|
||||
<div className="flex items-center mb-3">
|
||||
<CtpvSggPrcsFcltSelectBox
|
||||
sido={ctpvCd}
|
||||
sigungu={sggCd}
|
||||
onSidoChange={(sidoId: number) => { handleCtpvCdChange(sidoId) }}
|
||||
onSigunguChange={setSggCd}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center mb-6">
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">보고서 구분</Label>
|
||||
<SelectBox className="w-44 mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={[...reportGubuns]}
|
||||
value={reportGubun} onSelectValue={val => setReportGubun(String(val))}
|
||||
selectContentClass="z-[101]" labelClass='whitespace-nowrap text-xl text-gray-600 pr-2'
|
||||
/>
|
||||
{/* 승인년도 필터 */}
|
||||
<YearRangeSelectBox
|
||||
start={yearFilter.start}
|
||||
end={yearFilter.end}
|
||||
onChange={range => setYearFilter({ ...yearFilter, start: range.start, end: range.end })}
|
||||
startLabel="승인년도" endLabel=""
|
||||
/>
|
||||
{/* 조회 버튼 */}
|
||||
<div className="flex-1 flex justify-end">
|
||||
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-[79vh] w-full overflow-hidden rounded-lg border">
|
||||
{/* 테이블2 영역 (가로/세로 스크롤 가능) */}
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<UiTable
|
||||
headName={(() => {
|
||||
return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
|
||||
})()
|
||||
}
|
||||
tableData={tableData}
|
||||
headlist={dynamicHeadlist}
|
||||
pageSize={10}
|
||||
total={tableCount}
|
||||
cellClick={handleReportCellClick}
|
||||
onPageChange={setCurPage}
|
||||
resetPageNo={isResetPage}
|
||||
resetSelection={isResetSelection}
|
||||
/>
|
||||
<div className="h-[79vh] w-full overflow-hidden rounded-lg border">
|
||||
{/* 테이블2 영역 (가로/세로 스크롤 가능) */}
|
||||
<div className="flex-1 px-2 pb-2">
|
||||
<LoadingOverlay isLoading={isLoading} />
|
||||
<UiTable
|
||||
headName={(() => {
|
||||
return (<>조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
|
||||
})()
|
||||
}
|
||||
tableData={tableData}
|
||||
headlist={reportTableHeadlist}
|
||||
pageSize={10}
|
||||
total={tableCount}
|
||||
cellClick={handleReportCellClick}
|
||||
onPageChange={setCurPage}
|
||||
resetPageNo={isResetPage}
|
||||
resetSelection={isResetSelection}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
import {useState, useEffect} from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { getDcData } from '@/components/lib/api';
|
||||
|
||||
const ADDR_URL = process.env.NEXT_PUBLIC_ADDR_URL;
|
||||
|
||||
export type Folder = {
|
||||
clsfSystmSn: string;
|
||||
upClsfSystmSn: string;
|
||||
clsfSystmNm: string;
|
||||
children: Folder[];
|
||||
};
|
||||
|
||||
clsfSystmSn: string;
|
||||
upClsfSystmSn: string;
|
||||
clsfSystmNm: string;
|
||||
children: Folder[];
|
||||
};
|
||||
export type FlattenedFolder = {
|
||||
[key: string]: string|number|undefined;
|
||||
};
|
||||
|
||||
type SelectDataType = {
|
||||
text: string;
|
||||
val: string;
|
||||
sido?: string; // 시도 코드 (선택 사항)
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
export type SelectDataType = {
|
||||
text: string;
|
||||
val: string;
|
||||
sido?: string; // 시도 코드 (선택 사항)
|
||||
}
|
||||
|
||||
type ReportDataFile = {
|
||||
no : number;
|
||||
fileTitle : string;
|
||||
regDt : string;
|
||||
fileSn : string;
|
||||
filePath : string;
|
||||
no: number;
|
||||
fileTitle: string;
|
||||
regDt: string;
|
||||
fileSn: string;
|
||||
filePath: string;
|
||||
}
|
||||
const isYear = (value: string): boolean => {
|
||||
return /^[0-9]{4}년$/.test(value);
|
||||
};
|
||||
export const reportGubuns = [{ val: '0', text: '전체' }, { val: '1', text: '보고서' }
|
||||
, { val: '2', text: '요약보고서' }, { val: '3', text: '부록' }, { val: '4', text: '기타' }];
|
||||
|
||||
export const reportGubuns = [{val: '0', text: '전체'}, {val: '1', text: '보고서'}
|
||||
, {val: '2', text: '요약보고서'} , {val: '3', text: '부록'}, {val: '4', text: '기타'}];
|
||||
|
||||
|
||||
/**
|
||||
* Flatten a hierarchical folder structure into a flat array.
|
||||
* @param data - The hierarchical folder data to flatten.
|
||||
@@ -41,18 +37,20 @@ export const reportGubuns = [{val: '0', text: '전체'}, {val: '1', text: '보
|
||||
*/
|
||||
export function flattenFolders(data: Folder[]): FlattenedFolder[] {
|
||||
const result: FlattenedFolder[] = [];
|
||||
const exceptNodeNm = ["부분","통합","전체"];
|
||||
const addNodeNm = ["보고서","요약보고서","부록","기타"]; // level5
|
||||
const columnNms: { [key: number]: string } = { 0: "root", 1:"sido", 2:"sigungu", 3:"approvalYear"
|
||||
,4:"plantZoneGugun", 5:"reportGubun", 7:"fileName", 8:"regDate" };
|
||||
const exceptNodeNm = ["부분", "통합", "전체"];
|
||||
const addNodeNm = ["보고서", "요약보고서", "부록", "기타"]; // level5
|
||||
const columnNms: { [key: number]: string } = {
|
||||
0: "root", 1: "sido", 2: "sigungu", 3: "approvalYear"
|
||||
, 4: "plantZoneGubun", 5: "reportGubun", 7: "fileName", 8: "regDate"
|
||||
};
|
||||
|
||||
function traverse(
|
||||
node: Folder,
|
||||
path: { [key: string]: string| number },
|
||||
path: { [key: string]: string | number },
|
||||
level: number
|
||||
) {
|
||||
if (level === 2 && isYear(node.clsfSystmNm)) {
|
||||
level++;
|
||||
level++;
|
||||
}
|
||||
if (level === 4 && addNodeNm.includes(node.clsfSystmNm)) {
|
||||
level++; // 레벨을 증가시켜 하위값에 추가되도록 함.
|
||||
@@ -79,7 +77,7 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] {
|
||||
}
|
||||
}
|
||||
|
||||
if (data && data.length > 0 && data[0].clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리
|
||||
if (data && data.length > 0 && data[0]?.clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리
|
||||
data.forEach((root) => traverse(root, {}, 0));
|
||||
} else {
|
||||
data.forEach((root) => traverse(root, {}, 1));
|
||||
@@ -87,11 +85,11 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
export const getDcTree = async (clsfSystmNm:string) => {
|
||||
export const getDcTree = async (clsfSystmNm: string) => {
|
||||
const result = await getDcData("/api/v1/dc/clsf?clsfSystmNm=" + clsfSystmNm);
|
||||
return result;
|
||||
};
|
||||
export const useDcTree = (clsfSystmNm:string) => {
|
||||
export const useDcTree = (clsfSystmNm: string) => {
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -109,31 +107,31 @@ export const useDcTree = (clsfSystmNm:string) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getDcFile = async (clsfSystmSn:string) => {
|
||||
const queryString = 'clsfSystmSn='+clsfSystmSn;
|
||||
export const getDcFile = async (clsfSystmSn: string) => {
|
||||
const queryString = 'clsfSystmSn=' + clsfSystmSn;
|
||||
const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`);
|
||||
return result;
|
||||
};
|
||||
export const addFileName = async (data:FlattenedFolder[]) => {
|
||||
export const addFileName = async (data: FlattenedFolder[]) => {
|
||||
const fileReportData = await Promise.all(
|
||||
data.map(async (item) => {
|
||||
const filenameArr:ReportDataFile[] = await getDcFile(String(item.clsfSystmSn));
|
||||
const filenameArr: ReportDataFile[] = await getDcFile(String(item.clsfSystmSn));
|
||||
const f = filenameArr[0];
|
||||
const result = {...item, fileName:f?.fileTitle, regDate:f?.regDt, fileSn:f?.fileSn, filePath:f?.filePath};
|
||||
|
||||
const result = { ...item, fileName: f?.fileTitle, regDate: f?.regDt, fileSn: f?.fileSn, filePath: f?.filePath };
|
||||
|
||||
return result;
|
||||
})
|
||||
);
|
||||
return fileReportData;
|
||||
};
|
||||
export const getDcFileprcs = async (fileSn:string) => {
|
||||
const queryString = 'fileSn='+fileSn;
|
||||
export const getDcFileprcs = async (fileSn: string) => {
|
||||
const queryString = 'fileSn=' + fileSn;
|
||||
const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getDcFileDownload = async (filePath:string) => {
|
||||
const queryString = 'filePath='+ encodeURIComponent(filePath);
|
||||
export const getDcFileDownload = async (filePath: string) => {
|
||||
const queryString = 'filePath=' + encodeURIComponent(filePath);
|
||||
const response = await fetch(`/dc-service/api/v1/dc/file/download?${queryString}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
@@ -143,7 +141,7 @@ export const getDcFileDownload = async (filePath:string) => {
|
||||
const blob = await response.blob(); // PDF, image 등은 blob으로 받아야 함
|
||||
return blob;
|
||||
};
|
||||
export const fileDownload = (data:Blob, fileName:string) => { // Blob데이터 임시link로 연결
|
||||
export const fileDownload = (data: Blob, fileName: string) => { // Blob데이터 임시link로 연결
|
||||
const url = window.URL.createObjectURL(data);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@@ -153,80 +151,9 @@ export const fileDownload = (data:Blob, fileName:string) => { // Blob데이터
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
export const getFileDownload = async (fileSn:string, fileName:string, filePath:string) => {
|
||||
if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn"+ fileSn);
|
||||
export const getFileDownload = async (fileSn: string, fileName: string, filePath: string) => {
|
||||
if (filePath === "") alert("filePath 정보 없으므로 다운로드 불가:filesn" + fileSn);
|
||||
const blobData = await getDcFileDownload(filePath); // 실제 다운로드 API
|
||||
fileDownload(blobData, fileName);
|
||||
}
|
||||
// 시도 데이터
|
||||
export const getCityCd = async (): Promise<SelectDataType[]> => {
|
||||
try {
|
||||
const response = await fetch(`${ADDR_URL}/api/v1/addr/city`, { method: "GET", headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`시도 데이터 오류: ${response.status}`);
|
||||
const res = await response.json();
|
||||
const city = res.data;
|
||||
let citys:SelectDataType[] = city.map((item: {adstDstrCd:string, sido:string}) => ({
|
||||
val: item.adstDstrCd,
|
||||
text: item.sido,
|
||||
}));
|
||||
citys = citys.sort((a:SelectDataType, b:SelectDataType) => a.text.localeCompare(b.text));
|
||||
citys.unshift({ val: '0', text: '전체' });
|
||||
return citys;
|
||||
} catch (error) {
|
||||
console.error('시도 데이터를 가져오는데 실패했습니다:', error);
|
||||
return [{ val: '0', text: '전체', sido: '0' }];
|
||||
}
|
||||
};
|
||||
|
||||
export const useSggCd = () => {
|
||||
const [data, setData] = useState<SelectDataType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const result = await getSggCd();
|
||||
if (result) {
|
||||
setData(result);
|
||||
}
|
||||
console.log("useSggCd호출");
|
||||
};
|
||||
fetch();
|
||||
}, []);
|
||||
return data;
|
||||
};
|
||||
|
||||
// 시군구 데이터
|
||||
export const getSggCd = async (): Promise<SelectDataType[]> => {
|
||||
try {
|
||||
const response = await fetch(`${ADDR_URL}/api/v1/addr/sgg`, { method: "GET", headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`);
|
||||
const res = await response.json();
|
||||
const sgg = res.data;
|
||||
const sggCd = sgg.map((item: {adstDstrCd:string, sido:string, gugun:string}) => ({
|
||||
val: item.adstDstrCd,
|
||||
sido: item.sido,
|
||||
text: item.gugun,
|
||||
}));
|
||||
sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가
|
||||
return sggCd;
|
||||
} catch (error) {
|
||||
console.error('시군구 데이터를 가져오는데 실패했습니다:', error);
|
||||
return [{ val: '0', text: '전체', sido: '0' }];
|
||||
}
|
||||
};
|
||||
|
||||
export const useCityCd = () => {
|
||||
const [data, setData] = useState<SelectDataType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const result = await getCityCd();
|
||||
if (result) {
|
||||
setData(result);
|
||||
}
|
||||
console.log("useCityCd호출");
|
||||
};
|
||||
fetch();
|
||||
}, []);
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,46 +3,80 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { Label } from "@common/components/ui/label";
|
||||
|
||||
export function WaterDepth({ onClose }: { onClose?: () => void }) {
|
||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||
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;
|
||||
isPlaying: boolean;
|
||||
setIsPlaying: (isPlaying: boolean) => void;
|
||||
totalSteps?: number;
|
||||
currentStep?: number;
|
||||
}
|
||||
|
||||
export function WaterDepth({
|
||||
onClose,
|
||||
isPlaying,
|
||||
setIsPlaying,
|
||||
totalSteps = 0,
|
||||
currentStep = 0,
|
||||
}: WaterDepthProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (setIsPlaying) setIsPlaying(isPlaying);
|
||||
}, [isPlaying, setIsPlaying]);
|
||||
|
||||
const handlePlayToggle = () => {
|
||||
setIsPlaying(!isPlaying);
|
||||
};
|
||||
|
||||
// 진행률 계산 (0-100%)
|
||||
const progressPercentage = totalSteps > 0 ? (currentStep / totalSteps) * 100 : 100;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed top-8 left-8 z-50 bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
|
||||
<div className="flex items-center justify-between mx-2 my-4">
|
||||
<Label className="text-3xl text-gray-900 font-bold">침수심 범례</Label>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2"
|
||||
onClick={() => setIsPlaying((prev) => !prev)}
|
||||
>
|
||||
<button type="button" className="mt-1 ml-2" onClick={handlePlayToggle}>
|
||||
{isPlaying ? (
|
||||
<img
|
||||
src="/images/map/toolbar/pause_button.png"
|
||||
alt="pause"
|
||||
className="w-6 h-6 object-contain"
|
||||
/>
|
||||
<img src="/images/map/toolbar/pause_button.png" alt="pause" className="w-6 h-6 object-contain" />
|
||||
) : (
|
||||
<img
|
||||
src="/images/map/toolbar/play_button_arrowhead.png"
|
||||
alt="play"
|
||||
className="w-6 h-6 object-contain"
|
||||
/>
|
||||
<img src="/images/map/toolbar/play_button_arrowhead.png" alt="play" className="w-6 h-6 object-contain" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border-b border-gray-300 mb-2" />
|
||||
<div className="mb-3">
|
||||
<div className="w-full bg-gray-200 rounded-full h-1">
|
||||
<div
|
||||
className="bg-blue-900 h-1 rounded-full transition-all duration-300 ease-in-out"
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[140px] mx-auto flex flex-col items-center">
|
||||
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">침 수 심</div>
|
||||
<div className="space-y-1 w-full">
|
||||
<LegendItem color="#0000ff" label="0.3~0.5m 미만" />
|
||||
<LegendItem color="#00e8fe" label="0.5~1.0m 미만" />
|
||||
<LegendItem color="#00fdb7" label="1.0~1.5m 미만" />
|
||||
<LegendItem color="#00fe4e" label="1.5~2.0m 미만" />
|
||||
<LegendItem color="#fce800" label="2.0~3.0m 미만" />
|
||||
<LegendItem color="#fe0000" label="3.0m 이상" />
|
||||
<LegendItem color="rgb(255, 255, 127)" label="0.0~0.5m 미만" /> {/* 코드: N330 */}
|
||||
<LegendItem color="rgb(191, 255, 0)" label="0.5~1.0m 미만" /> {/* 코드: N331 */}
|
||||
<LegendItem color="rgb(0, 255, 255)" label="1.0~2.0m 미만" /> {/* 코드: N332 */}
|
||||
<LegendItem color="rgb(191, 127, 255)" label="2.0~5.0m 미만" /> {/* 코드: N333 */}
|
||||
<LegendItem color="rgb(255, 0, 127)" label="5.0m 이상" /> {/* 코드: N334 */}
|
||||
</div>
|
||||
<div className="border-t-4 border-gray-900 mt-2 mx-auto w-full" />
|
||||
<div className="border-t-2 border-blue-900 mt-1 mx-auto w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { deleteData, getData } from '@/components/lib/api'
|
||||
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
|
||||
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState, useEffect } from 'react'
|
||||
@@ -48,6 +48,7 @@ const tableColumn = [
|
||||
]
|
||||
|
||||
const Result = () => {
|
||||
const AUTH = useCookieAuth();
|
||||
|
||||
const form = useForm<ResultFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -123,6 +124,7 @@ const Result = () => {
|
||||
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
|
||||
"sggCd": sggCd === 0 ? null : sggCd,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
"auth": AUTH,
|
||||
};
|
||||
|
||||
console.log("Search DTO:", inftwtAnlsSearchDto);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { deleteData, getData } from '@/components/lib/api'
|
||||
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
|
||||
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState, useEffect } from 'react'
|
||||
@@ -46,6 +46,8 @@ const tableColumn = [
|
||||
]
|
||||
|
||||
const Result = () => {
|
||||
const AUTH = useCookieAuth();
|
||||
|
||||
const form = useForm<ResultFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -119,6 +121,7 @@ const Result = () => {
|
||||
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
|
||||
"sggCd": sggCd === 0 ? null : sggCd,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
"auth": AUTH,
|
||||
};
|
||||
|
||||
console.log("Search DTO:", inftwtAnlsSearchDto);
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { SelectBox } from '@common/business_components/ui/select';
|
||||
import { Label } from '@radix-ui/react-label';
|
||||
|
||||
export type comCodeState = {
|
||||
code: {
|
||||
isInit: boolean;
|
||||
chBuCd: SelectDataType[]
|
||||
rainFcltCd: SelectDataType[];
|
||||
bdstUsgCd: SelectDataType[];
|
||||
cityCd: SelectDataType[];
|
||||
sggCd: SelectDataType[];
|
||||
rvbsnCd: SelectDataType[];
|
||||
mdmbsnCd: SelectDataType[];
|
||||
lgbsnCd: SelectDataType[];
|
||||
lclgvrnmntCd: EnvPartSelectDataType[];
|
||||
orgwtKndCd: SelectDataType[];
|
||||
yn: SelectDataType[];
|
||||
buildingType: {
|
||||
RST: string,
|
||||
RNB: string,
|
||||
HTB: string
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type SelectDataType = {
|
||||
text: string;
|
||||
val: string;
|
||||
sido?: string; // 시도 코드 (선택 사항)
|
||||
}
|
||||
|
||||
type EnvPartSelectDataType = SelectDataType & {
|
||||
adstDliStrCd: string,
|
||||
}
|
||||
|
||||
type SearchFormLeftProps = {
|
||||
isShowPart?: boolean | undefined;
|
||||
children: React.ReactNode;
|
||||
selectSidoData?: SelectDataType[] | [];
|
||||
selectSigunData?: SelectDataType[] | [];
|
||||
selectRegionData?: SelectDataType[] | [];
|
||||
selectUnitWaterShedData?: SelectDataType[] | [];
|
||||
selectMiddleRegionData?: SelectDataType[] | [];
|
||||
selectEnvPartData?: EnvPartSelectDataType[] | [];
|
||||
selectEnvPartSidoData?: SelectDataType[] | [];
|
||||
selectEnvPartSigunData?: SelectDataType[] | [];
|
||||
|
||||
setRtnPartValue?: (value: string) => void;
|
||||
setRtnSidoValue?: (value: string) => void;
|
||||
setRtnSigunValue?: (value: string) => void;
|
||||
setRtnRegionValue?: (value: string) => void;
|
||||
setRtnUnitWaterShedValue?: (value: string) => void;
|
||||
setRtnMiddleRegionValue?: (value: string) => void;
|
||||
setRtnEnvPartValue?: (value: string) => void;
|
||||
setRtnEnvPartSidoValue?: (value: string) => void;
|
||||
setRtnEnvPartSigunValue?: (value: { val: string, text: string }) => void; // setRtnEnvPartSigunValue prop 타입을 객체로 변경
|
||||
resetTrigger?: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function GovSidoSigunguSearchForm({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSigunValue, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false }: SearchFormLeftProps) {
|
||||
//환경청별 시도 시군구
|
||||
const [envPartValue, setEnvPartValue] = useState<string>('0');
|
||||
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>('0'); // 환경청 선택 시 시/도 값
|
||||
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>('0'); // 환경청 선택 시 시/군/구 값
|
||||
|
||||
const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState<SelectDataType[]>([]);
|
||||
|
||||
// API로부터 가져온 전체 데이터 목록 (필터링 전)
|
||||
const [allEnvPartOptions, setAllEnvPartOptions] = useState<EnvPartSelectDataType[]>([]);
|
||||
const [allSidoOptions, setAllSidoOptions] = useState<SelectDataType[]>([]);
|
||||
const [allSigunOptions, setAllSigunOptions] = useState<SelectDataType[]>([]);
|
||||
|
||||
// 조회된 조건에 맞는 환경청 시도 시군 (SelectBox에 실제 사용될 데이터)
|
||||
const [filteredEnvPartData, setFilteredEnvPartData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }]);
|
||||
const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }]);
|
||||
const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0', sido: '0' }]);
|
||||
|
||||
// 시군구 데이터
|
||||
const getSggCd = async (): Promise<SelectDataType[]> => {
|
||||
try {
|
||||
const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/sgg', { method: "GET", headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`시군구 데이터 오류: ${response.status}`);
|
||||
const res = await response.json();
|
||||
const sgg = res.data;
|
||||
const sggCd = sgg.map((item: any) => ({
|
||||
val: item.adstDstrCd,
|
||||
sido: item.sido,
|
||||
text: item.gugun,
|
||||
}));
|
||||
sggCd.unshift({ val: '0', text: '전체' }); // "전체" 옵션 추가
|
||||
return sggCd;
|
||||
} catch (error) {
|
||||
console.error('시군구 데이터를 가져오는데 실패했습니다:', error);
|
||||
return [{ val: '0', text: '전체', sido: '0' }];
|
||||
}
|
||||
};
|
||||
|
||||
// 시도 데이터
|
||||
const getCityCd = async (): Promise<SelectDataType[]> => {
|
||||
try {
|
||||
const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/city', { method: "GET", headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`시도 데이터 오류: ${response.status}`);
|
||||
const res = await response.json();
|
||||
const city = res.data;
|
||||
const cityCd = city.map((item: any) => ({
|
||||
val: item.adstDstrCd,
|
||||
text: item.sido,
|
||||
}));
|
||||
cityCd.unshift({ val: '0', text: '전체' });
|
||||
return cityCd;
|
||||
} catch (error) {
|
||||
console.error('시도 데이터를 가져오는데 실패했습니다:', error);
|
||||
return [{ val: '0', text: '전체', sido: '0' }];
|
||||
}
|
||||
};
|
||||
|
||||
// 환경청 데이터
|
||||
const getEnvPartCd = async (): Promise<EnvPartSelectDataType[]> => {
|
||||
try {
|
||||
const response = await fetch('http://con.mind-one.co.kr:11082/api/v1/addr/govArea', { method: "GET", headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`환경청 데이터 오류: ${response.status}`);
|
||||
const res = await response.json();
|
||||
const lclgvrnmnt = res.data.filter((item: { lclgvrnmntNm: string; }) => {
|
||||
// item.lclgvrnmntNm이 null이나 undefined가 아닌지 확인 후 endsWith 사용
|
||||
return item.lclgvrnmntNm && item.lclgvrnmntNm.endsWith("환경청");
|
||||
});
|
||||
const lclgvrnmntCd = lclgvrnmnt.map((item: any) => ({
|
||||
val: item.lclgvrnmntCd,
|
||||
text: item.lclgvrnmntNm,
|
||||
adstDliStrCd: item.adstDliStrCd || ''
|
||||
}));
|
||||
lclgvrnmntCd.unshift({ val: '0', text: '전체', adstDliStrCd: '' });
|
||||
return lclgvrnmntCd;
|
||||
} catch (error) {
|
||||
console.error('환경청 데이터를 가져오는데 실패했습니다:', error);
|
||||
return [{ val: '0', text: '전체', adstDliStrCd: '', sido: '0' }];
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadInitialData = async () => {
|
||||
const [envParts, sidos, siguns] = await Promise.all([
|
||||
getEnvPartCd(),
|
||||
getCityCd(),
|
||||
getSggCd()
|
||||
]);
|
||||
|
||||
setAllEnvPartOptions(envParts.filter(item => item.val !== '0'));
|
||||
setFilteredEnvPartData(envParts);
|
||||
|
||||
setAllSidoOptions(sidos.filter(item => item.val !== '0'));
|
||||
// 초기 filteredEnvPartSidoData는 모든 시/도를 포함 (환경청 "전체" 선택 시)
|
||||
setFilteredEnvPartSidoData(sidos.map(s => ({ ...s, adstDliStrCd: '' })));
|
||||
|
||||
|
||||
setAllSigunOptions(siguns.filter(item => item.val !== '0'));
|
||||
// 초기 filteredEnvPartSigunData는 모든 시/군/구를 포함 (환경청 "전체", 시/도 "전체" 선택 시)
|
||||
setFilteredEnvPartSigunData(siguns);
|
||||
|
||||
// 만약 isShowPart가 true이고, partValue가 'G'(환경청별)로 기본 설정되어 있다면,
|
||||
// 초기 envPartValue ('0')에 대한 시도/시군구 목록을 설정합니다.
|
||||
initializeCityDataByEnvPartSido(envPartValue); // envPartValue 기본값 '0'
|
||||
};
|
||||
|
||||
if (isShowPart) {
|
||||
loadInitialData();
|
||||
}
|
||||
}, [isShowPart]); // isShowPart가 변경될 때 데이터를 다시 로드
|
||||
|
||||
// 환경청 세팅 - 시도
|
||||
const initializeCityDataByEnvPartSido = (envPartCd: string) => {
|
||||
const foundEnvPart = allEnvPartOptions.find((item: EnvPartSelectDataType) => item.val === envPartCd);
|
||||
console.log('foundEnvPart:', foundEnvPart);
|
||||
|
||||
let relevantSidos: SelectDataType[] = []; // 기본값을 빈 배열로 설정
|
||||
let relevantSigunsForSidoDerivation: SelectDataType[] = []; // 기본값을 빈 배열로 설정
|
||||
|
||||
if (envPartCd === '0') { // "전체" 환경청 선택 시
|
||||
relevantSidos = [...allSidoOptions];
|
||||
relevantSigunsForSidoDerivation = [...allSigunOptions];
|
||||
} else if (foundEnvPart && foundEnvPart.adstDliStrCd) { // 특정 환경청 선택 & 해당 환경청에 adstDliStrCd 정보가 있는 경우
|
||||
// adstDliStrCd가 시도 코드를 포함한다고 가정 (주석 참고)
|
||||
const cityCodesInEnvPart = foundEnvPart.adstDliStrCd.toString().split('|').join('').split(',').filter(c => c);
|
||||
console.log('cityCodesInEnvPart:', cityCodesInEnvPart);
|
||||
// 1. allSigunOptions에서 val이 cityCodesInEnvPart에 포함된 데이터의 sido만 추출
|
||||
const relevantSigunSidos = Array.from(
|
||||
new Set(
|
||||
allSigunOptions
|
||||
.filter(sigungu => cityCodesInEnvPart.includes(sigungu.val))
|
||||
.map(sigungu => sigungu.sido)
|
||||
)
|
||||
);
|
||||
console.log('relevantSigunTexts:', relevantSigunSidos);
|
||||
|
||||
// 2. relevantSigunsForSidoDerivation은 해당 sido만을 가진 배열로 반환
|
||||
relevantSidos = allSidoOptions.filter(sido => relevantSigunSidos.includes(sido.text));
|
||||
console.log('relevantSidos:', relevantSidos);
|
||||
|
||||
// 해당 시도들에 속하는 시군구들 (가나다순 오름차순 정렬)
|
||||
relevantSigunsForSidoDerivation = allSigunOptions
|
||||
.filter(sigun => {
|
||||
const sigunSidoPrefix = sigun.val.substring(0, 2);
|
||||
return relevantSidos.some(s => s.val === sigunSidoPrefix);
|
||||
})
|
||||
.sort((a, b) => a.text.localeCompare(b.text, 'ko'));
|
||||
}
|
||||
// 특정 환경청이 선택되었으나, foundEnvPart가 없거나 adstDliStrCd 정보가 없는 경우,
|
||||
// relevantSidos와 relevantSigunsForSidoDerivation은 초기 빈 배열 상태를 유지합니다.
|
||||
|
||||
setFilteredEnvPartSidoData([
|
||||
{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' },
|
||||
...relevantSidos.map((item: SelectDataType) => ({ text: item.text, val: item.val, adstDliStrCd: '', sido: '0' }))
|
||||
]);
|
||||
|
||||
setFilteredEnvPartSigunDataSelected(relevantSigunsForSidoDerivation);
|
||||
|
||||
// 시/도 선택이 "전체"('0')로 초기화되므로, 해당 시/군/구 목록도 업데이트
|
||||
initializeCityDataByEnvPartSigun('0');
|
||||
}
|
||||
|
||||
// 환경청 세팅 - 시군구
|
||||
const initializeCityDataByEnvPartSigun = (selectedSidoInEnvPart: string) => {
|
||||
let sigunsToList: SelectDataType[];
|
||||
if (selectedSidoInEnvPart === '0') { // 선택된 시도가 "전체"이면
|
||||
// filteredEnvPartSigunDataSelected는 이미 해당 환경청에 맞는 시군구들로 필터링 되어있음
|
||||
sigunsToList = [...filteredEnvPartSigunDataSelected];
|
||||
} else {
|
||||
sigunsToList = filteredEnvPartSigunDataSelected.filter((item: SelectDataType) => item.val.substring(0, 2) === selectedSidoInEnvPart);
|
||||
}
|
||||
setFilteredEnvPartSigunData([
|
||||
{ text: "전체", val: '0' },
|
||||
...sigunsToList
|
||||
]);
|
||||
}
|
||||
|
||||
const onSelectValue = (val: string, type: string) => {
|
||||
switch (type) {
|
||||
// 환경청 세팅
|
||||
case 'envPart':
|
||||
setEnvPartValue(val);
|
||||
setRtnEnvPartValue?.(val);
|
||||
initializeCityDataByEnvPartSido(val);
|
||||
setEnvPartSidoValue('0'); // 시도 "전체"로 초기화
|
||||
setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화
|
||||
// 부모에게도 초기화된 값 전달 (text 포함)
|
||||
if (setRtnEnvPartSidoValue) {
|
||||
const sidoDefault = filteredEnvPartSidoData.find(item => item.val === '0') || { val: '0', text: '전체' };
|
||||
// setRtnEnvPartSidoValue(sidoDefault.val); // 이전 방식: 값만 전달
|
||||
// TODO: 만약 시도 값도 객체로 전달해야 한다면 아래와 같이 수정
|
||||
// setRtnEnvPartSidoValue({ val: sidoDefault.val, text: sidoDefault.text });
|
||||
}
|
||||
if (setRtnEnvPartSigunValue) {
|
||||
const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' };
|
||||
setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text });
|
||||
}
|
||||
break;
|
||||
case 'envPartSido': // 환경청별 - 시도 선택
|
||||
setEnvPartSidoValue(val);
|
||||
setRtnEnvPartSidoValue?.(val);
|
||||
initializeCityDataByEnvPartSigun(val);
|
||||
setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화
|
||||
if (setRtnEnvPartSigunValue) {
|
||||
const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' };
|
||||
setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text });
|
||||
}
|
||||
break;
|
||||
case 'envPartSigun': // 환경청별 - 시군구 선택
|
||||
setEnvPartSigunValue(val);
|
||||
const selectedSigun = filteredEnvPartSigunData.find(item => item.val === val);
|
||||
if (setRtnEnvPartSigunValue && selectedSigun) {
|
||||
setRtnEnvPartSigunValue({ val: selectedSigun.val, text: selectedSigun.text });
|
||||
} else if (setRtnEnvPartSigunValue && val === '0') { // "전체"를 명시적으로 처리 (find 실패 대비)
|
||||
setRtnEnvPartSigunValue({ val: '0', text: '전체' });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 리셋 함수
|
||||
const resetAllValues = () => {
|
||||
|
||||
setEnvPartValue('0');
|
||||
setEnvPartSidoValue('0');
|
||||
setEnvPartSigunValue('0');
|
||||
|
||||
// 필터링된 목록들도 초기 "전체" 또는 전체 목록으로 리셋
|
||||
setFilteredEnvPartSigunData([{ text: "전체", val: '0', sido: '0' }, ...allSigunOptions]);
|
||||
|
||||
setFilteredEnvPartData([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }, ...allEnvPartOptions]);
|
||||
setFilteredEnvPartSidoData([{ text: "전체", val: '0', adstDliStrCd: '', sido: '0' }, ...allSidoOptions.map(s => ({ ...s, adstDliStrCd: '' }))]);
|
||||
setFilteredEnvPartSigunData([{ text: "전체", val: '0', sido: '0' }, ...allSigunOptions]);
|
||||
setFilteredEnvPartSigunDataSelected(allSigunOptions);
|
||||
|
||||
|
||||
setRtnPartValue?.('A');
|
||||
setRtnSidoValue?.('0');
|
||||
setRtnSigunValue?.('0');
|
||||
setRtnRegionValue?.('0');
|
||||
setRtnUnitWaterShedValue?.('0');
|
||||
setRtnMiddleRegionValue?.('0');
|
||||
setRtnEnvPartValue?.('0');
|
||||
setRtnEnvPartSidoValue?.('0');
|
||||
setRtnEnvPartSigunValue?.({ val: '0', text: '전체' });
|
||||
};
|
||||
|
||||
// 리셋 트리거 변경 감지
|
||||
useEffect(() => {
|
||||
if (resetTrigger) {
|
||||
resetAllValues();
|
||||
// 리셋 후 트리거를 false로 설정하는 로직은 부모 컴포넌트에서 처리해야 할 수 있습니다.
|
||||
// 여기서는 resetTrigger props가 true일 때 리셋을 수행합니다.
|
||||
}
|
||||
}, [resetTrigger]);
|
||||
|
||||
return (
|
||||
isShowPart ? (
|
||||
<div className="flex items-center my-4">
|
||||
<>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap ml-2 mr-4">환경청</Label>
|
||||
<SelectBox
|
||||
className="w-[160px] mr-8"
|
||||
label={''}
|
||||
selectArray={filteredEnvPartData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), 'envPart')}
|
||||
value={envPartValue}
|
||||
selectClass={'rounded-lg bg-white truncate'}
|
||||
/>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">시도</Label>
|
||||
<SelectBox
|
||||
className="w-[160px] mr-8"
|
||||
label={''}
|
||||
selectArray={filteredEnvPartSidoData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSido')}
|
||||
value={envPartSidoValue}
|
||||
selectClass={'rounded-lg bg-white truncate'}
|
||||
/>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">시군구</Label>
|
||||
<SelectBox
|
||||
className="w-[160px] mr-8"
|
||||
label={''}
|
||||
selectArray={filteredEnvPartSigunData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSigun')}
|
||||
value={envPartSigunValue}
|
||||
selectClass={'rounded-lg bg-white truncate'}
|
||||
/>
|
||||
</>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export { GovSidoSigunguSearchForm };
|
||||
@@ -9,6 +9,7 @@ type ModalType = {
|
||||
noHeaderYn?: 'N' | 'Y';
|
||||
noFooterYn?: 'N' | 'Y';
|
||||
headerTitle?: string;
|
||||
headerTitleClass?: string;
|
||||
footerContent?: React.ReactNode;
|
||||
useDirectRender?: boolean;
|
||||
}
|
||||
@@ -21,6 +22,7 @@ export function UiModal({
|
||||
noHeaderYn = 'N',
|
||||
noFooterYn = 'N',
|
||||
headerTitle,
|
||||
headerTitleClass,
|
||||
footerContent,
|
||||
useDirectRender = false
|
||||
}: ModalType) {
|
||||
@@ -31,7 +33,7 @@ export function UiModal({
|
||||
<DialogContent className={bodyClass}>
|
||||
{noHeaderYn === 'N' && (
|
||||
<DialogHeader>
|
||||
<DialogDescription>
|
||||
<DialogDescription className={headerTitleClass}>
|
||||
{headerTitle}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -116,13 +116,20 @@ function Gis({
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
waterDepth: WaterDepth,
|
||||
height:height="h-[calc(100vh-10rem)]",
|
||||
waterDepth: WaterDepth,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
height: height = "h-[calc(100vh-10rem)]",
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [layerNm, setLayerNm] = useState("");
|
||||
const [totalSteps, setTotalSteps] = useState(0);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
// const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
// const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터
|
||||
// const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
@@ -275,6 +282,12 @@ function Gis({
|
||||
[onCheckedLayersChange],
|
||||
);
|
||||
|
||||
// 진행률 변경 핸들러
|
||||
const handleProgressChange = useCallback((total, current) => {
|
||||
setTotalSteps(total);
|
||||
setCurrentStep(current);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider
|
||||
@@ -305,8 +318,26 @@ function Gis({
|
||||
onFeatureSelect(dummyFeature);
|
||||
}}
|
||||
imageVisible={imgVisble}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying}
|
||||
targetLayerTypes={targetLayerTypes}
|
||||
orderedLayers={orderedLayers}
|
||||
checkInterval={checkInterval}
|
||||
onProgressChange={handleProgressChange}
|
||||
/>
|
||||
|
||||
{typeof WaterDepth === "function" && (
|
||||
<div className={`absolute bottom-4 z-40 ${tabVisible ? 'left-[260px]' : 'left-4'}`}>
|
||||
<WaterDepth
|
||||
onClose={() => setIsOpen(false)}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying}
|
||||
totalSteps={totalSteps}
|
||||
currentStep={currentStep}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{isOpen && !isSearchModalOpen && (
|
||||
<DetailView
|
||||
@@ -335,7 +366,6 @@ function Gis({
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
{typeof WaterDepth === "function" && <WaterDepth onClose={() => setIsOpen(false)} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -343,6 +373,7 @@ function Gis({
|
||||
<div className="absolute right-8 top-[305px] z-20">
|
||||
<button
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
title="정보 검색"
|
||||
className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]"
|
||||
>
|
||||
<Search size={24} className="text-gray-700" />
|
||||
|
||||
@@ -5,18 +5,37 @@ import {
|
||||
DndProvider
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
|
||||
import { useGisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick,imageVisible=false }) {
|
||||
export default React.memo(function GisTree({
|
||||
checkedNodes = [],
|
||||
setCheckedNodes = () => { },
|
||||
visible,
|
||||
setVisible,
|
||||
onCheckedChange,
|
||||
onLayerIconClick,
|
||||
imageVisible = false,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
onProgressChange = () => { },
|
||||
}) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
const [checked, setChecked] = useState([]);
|
||||
const [expanded, setExpanded] = useState([]);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
|
||||
const currentAnimationIndexRef = useRef(0);
|
||||
const targetNodesRef = useRef([]); // 대상 노드들을 저장할 ref
|
||||
const isPausedRef = useRef(false); // 일시정지 상태를 저장할 ref
|
||||
const timeoutIdRef = useRef(null); // setTimeout ID를 저장할 ref
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
const nodeData = setNodeValues(treeData);
|
||||
@@ -188,11 +207,182 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
return image;
|
||||
};
|
||||
const handleLayerImgClick = (nodeNm, nodeId, layerNm) => {
|
||||
if(!checked.includes(nodeId)) alert('시설을 선택해주세요')
|
||||
if (!checked.includes(nodeId)) alert('시설을 선택해주세요')
|
||||
else onLayerIconClick(nodeNm)
|
||||
}
|
||||
|
||||
// 특정 노드를 체크/언체크하는 함수
|
||||
const checkSpecificNode = useCallback((nodeId, shouldCheck = true) => {
|
||||
setChecked((prevChecked) => {
|
||||
let newChecked = [...prevChecked];
|
||||
const isCurrentlyChecked = prevChecked.includes(nodeId);
|
||||
|
||||
if (shouldCheck && !isCurrentlyChecked) {
|
||||
// 체크하기
|
||||
const descendants = getDescendants(nodeId, nodes);
|
||||
newChecked = Array.from(new Set([...newChecked, nodeId, ...descendants]));
|
||||
const ancestors = getAncestors(nodeId, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (isEveryChildChecked(p, nodes, newChecked)) {
|
||||
newChecked.push(p);
|
||||
}
|
||||
});
|
||||
} else if (!shouldCheck && isCurrentlyChecked) {
|
||||
// 언체크하기
|
||||
const descendants = getDescendants(nodeId, nodes);
|
||||
newChecked = newChecked.filter((cid) => cid !== nodeId && !descendants.includes(cid));
|
||||
const ancestors = getAncestors(nodeId, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (newChecked.includes(p)) {
|
||||
newChecked = newChecked.filter((cid) => cid !== p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 지도에 레이어 표시/숨김 적용
|
||||
const allIds = nodes.map((n) => n.id);
|
||||
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
|
||||
gisCore.setLayerOnMap(newChecked, true);
|
||||
gisCore.setLayerOnMap(unchecked, false);
|
||||
|
||||
// 상위로 변경 사항 전달
|
||||
const checkedNodesData = nodes.filter(node => newChecked.includes(node.id));
|
||||
onCheckedChange?.(newChecked, checkedNodesData);
|
||||
|
||||
return newChecked;
|
||||
});
|
||||
}, [nodes]);
|
||||
|
||||
// 침수위험지도 실행 함수
|
||||
const handleAutoCheckAnimation = useCallback((isEnabled, layerTypes = [], interval = 3000) => {
|
||||
// 기존 타임아웃 정리
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = null;
|
||||
}
|
||||
|
||||
if (!isEnabled || layerTypes.length === 0) {
|
||||
// 애니메이션 비활성화 시 상태 초기화
|
||||
isPausedRef.current = false;
|
||||
targetNodesRef.current = [];
|
||||
currentAnimationIndexRef.current = 0;
|
||||
onProgressChange(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// 대상 노드들을 찾아서 특정 순서로 정렬 후 배열로 반환
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
return orderedNodes;
|
||||
};
|
||||
|
||||
const targetNodes = getTargetNodesInOrder();
|
||||
|
||||
if (targetNodes.length === 0) {
|
||||
console.warn('대상 레이어를 찾을 수 없습니다:', layerTypes);
|
||||
return;
|
||||
}
|
||||
|
||||
// 처음 시작하는 경우에만 초기화
|
||||
if (targetNodesRef.current.length === 0) {
|
||||
targetNodesRef.current = targetNodes;
|
||||
currentAnimationIndexRef.current = 0;
|
||||
|
||||
console.log('자동 체크 대상 노드들 (순서대로):', targetNodes.map(n => ({ id: n.id, name: n.text, type: n.data?.lyrTyNm })));
|
||||
|
||||
// 진행률 초기화 (전체 단계 수 설정)
|
||||
onProgressChange(targetNodes.length, 0);
|
||||
|
||||
// 모든 대상 레이어를 먼저 언체크
|
||||
targetNodes.forEach(node => {
|
||||
checkSpecificNode(node.id, false);
|
||||
});
|
||||
}
|
||||
|
||||
// 애니메이션 재개/시작
|
||||
isPausedRef.current = false;
|
||||
|
||||
const processNextLayer = () => {
|
||||
// 일시정지 상태이거나 애니메이션이 비활성화되면 중단
|
||||
if (isPausedRef.current || !isPlaying) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTargetNodes = targetNodesRef.current;
|
||||
const currentIndex = currentAnimationIndexRef.current;
|
||||
|
||||
if (currentIndex < currentTargetNodes.length) {
|
||||
checkSpecificNode(currentTargetNodes[currentIndex].id, true);
|
||||
if (currentIndex > 0) {
|
||||
checkSpecificNode(currentTargetNodes[currentIndex - 1].id, false);
|
||||
}
|
||||
|
||||
currentAnimationIndexRef.current++;
|
||||
|
||||
// 진행률 업데이트
|
||||
onProgressChange(currentTargetNodes.length, currentAnimationIndexRef.current);
|
||||
|
||||
// 다음 레이어 처리를 위한 타이머 설정
|
||||
timeoutIdRef.current = setTimeout(processNextLayer, interval);
|
||||
} else {
|
||||
// 애니메이션 완료 - 상태 초기화 및 완료 신호 전송
|
||||
console.log('애니메이션 완료');
|
||||
isPausedRef.current = false;
|
||||
targetNodesRef.current = [];
|
||||
currentAnimationIndexRef.current = 0;
|
||||
timeoutIdRef.current = null;
|
||||
|
||||
// 진행률 100% 완료
|
||||
onProgressChange(currentTargetNodes.length, currentTargetNodes.length);
|
||||
|
||||
// 잠시 후 진행률 초기화
|
||||
setTimeout(() => {
|
||||
onProgressChange(0, 0);
|
||||
}, 1000);
|
||||
|
||||
setIsPlaying?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
processNextLayer();
|
||||
|
||||
}, [nodes, checkSpecificNode, setIsPlaying, isPlaying, onProgressChange]);
|
||||
|
||||
// 애니메이션 일시정지 함수
|
||||
const pauseAnimation = useCallback(() => {
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = null;
|
||||
}
|
||||
isPausedRef.current = true;
|
||||
}, []);
|
||||
|
||||
// 외부에서 전달받은 props로 자동 체크 제어
|
||||
useEffect(() => {
|
||||
if (isPlaying) handleAutoCheckAnimation(true, targetLayerTypes, checkInterval); // 재생 시작/재개
|
||||
else pauseAnimation(); // 일시정지
|
||||
}, [isPlaying, handleAutoCheckAnimation, targetLayerTypes, checkInterval, pauseAnimation]);
|
||||
|
||||
// 컴포넌트 언마운트 시 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -223,7 +413,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" />
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
|
||||
{imageVisible ? depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{handleLayerImgClick(node.text, node.id, node.data.lyrTyNm)}} alt={node.text}></img></span> : null}
|
||||
{imageVisible ? depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={() => { handleLayerImgClick(node.text, node.id, node.data.lyrTyNm) }} alt={node.text}></img></span> : null}
|
||||
</div>
|
||||
)}
|
||||
sort={(a, b) => {
|
||||
|
||||
Generated
-10995
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user