diff --git a/apps/sn/app/(map-layout)/map/components/common/table.ts b/apps/sn/app/(map-layout)/map/components/common/table.ts index 87be5bf6e..045b8db59 100644 --- a/apps/sn/app/(map-layout)/map/components/common/table.ts +++ b/apps/sn/app/(map-layout)/map/components/common/table.ts @@ -648,4 +648,42 @@ export const untrtSwgRainCol5 = [ { id: 'mesureBod', title: 'BOD(mg/L)' }, { id: 'mesureToc', title: 'TOC(mg/L)' }, { id: 'mesureLoad', title: '오염부하량(kg)' } +]; + +// 🎯 날짜 필드들 (yyyymmdd -> yyyy년 mm월 dd일) +export const dateFields = [ + 'instl_ymd', 'last_drdgn_ymd', 'cmcn_ymd', 'oprtng_strt_ymd', 'clsg_ymd', + 'mesure_begin_dt', 'mesure_end_dt', 'qltwtr_begin_dt', 'qltwtr_end_dt', + 'rain_begin_dt', 'rain_end_dt', 'mesureDt', + // 🎯 camelCase 버전 추가 + 'instlYmd', 'lastDrdgnYmd', 'cmcnYmd', 'oprtngStrtYmd', 'clsgYmd', + 'mesureBeginDt', 'mesureEndDt', 'qltwtrBeginDt', 'qltwtrEndDt', + 'rainBeginDt', 'rainEndDt' +]; + +// 🎯 연월 필드들 (yyyymm -> yyyy년 m월) +export const yearMonthFields = ['aplcnYm',]; + +// 🎯 날짜시간 필드들 (yyyy-mm-dd hh:mm:ss -> yyyy년 m월 d일 hh:mm) +export const dateTimeFields = ['regDt',]; + +// 🎯 숫자 필드들 (천 단위 구분자) +export const numberFields = [ + 'pdmt', 'prlg', 'avg_grdn', 'drtw_mltv_area', 'excln_mltv_area', 'tmpt_dpth', 'scend_dpth', 'trfln_cnt', 'vrtc_len', 'wdth_len', + 'fclt_cpct', 'plln_area', 'prcs_zone_area_sum', 'swtrmt_ppltn_cnt', 'stty_crtr_bod', 'stty_crtr_toc', 'stty_crtr_ss', 'stty_crtr_tn', 'stty_crtr_tph', 'stty_crtr_clbcl_cnt', + 'swage_mh_diam', 'wdth_len', 'vrtc_len', 'altd_vl', 'dpthlVl', + 'plln_area', 'daily_prcs_cpct', 'max_impnd_cpct', 'altd_vl', 'wtlv_vl', 'excln_wpmp_ablt', + 'worflw_wall_hgt', + 'swr_ppl_eqp_ar', 'swr_trt_popl', 'weir_hg', 'weir_lt', + 'precdng_dry', 'rain_fall', 'cntnc_time', 'rain_in', + 'mesure_intrvl', 'qltwtr_co', 'mesureQy', 'mesureBod', 'mesureToc', 'mesureLoad', + // 🎯 camelCase 버전 추가 + 'avgGrdn', 'drtwMltvArea', 'exclnMltvArea', 'tmptDpth', 'scendDpth', 'trflnCnt', 'vrtcLen', 'wdthLen', + 'fcltCpct', 'pllnArea', 'prcsZoneAreaSum', 'swtrmtPpltnCnt', 'sttyCltrBod', 'sttyCltrToc', 'sttyCltrSs', 'sttyCltrTn', 'sttyCltrTph', 'sttyCltrClbclCnt', + 'swageMhDiam', 'altdVl', 'dpthlVl', + 'dailyPrcsCpct', 'maxImpndCpct', 'wtlvVl', 'exclnWpmpAblt', + 'worflwWallHgt', + 'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt', + 'precdngDry', 'rainFall', 'cntncTime', 'rainIn', + 'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc' ]; \ No newline at end of file diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx index 0da4ab31e..d8f004cac 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/sewagePump.tsx @@ -13,7 +13,7 @@ const drtwInputColumn = [ ] // 하수펌프장 -const sewagePump = ({ featureData, isDrtwSewagePump, onSaveClick }: { featureData: any, isDrtwSewagePump: boolean, onSaveClick?: () => void }) => { +const sewagePump = ({ featureData, isDrtwSewagePump, onSaveClick }: { featureData: any, isDrtwSewagePump?: boolean, onSaveClick?: () => void }) => { const [formData, setFormData] = useState({}); // featureData가 변경될 때 formData 초기화 diff --git a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx index 95cab06c7..a3dfdce87 100644 --- a/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx +++ b/apps/sn/app/(map-layout)/map/featureClickEventPopup.tsx @@ -10,7 +10,8 @@ import { SwtrmtFcltCol, SwtrmtPrcsZoneCol, untrtSwgRainCol1, untrtSwgRainCol2, M import InfilChart from './components/popupComponent/chart/infilChart'; import RdiiChart from './components/popupComponent/chart/rdiiChart'; import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore"; -import { set } from "react-hook-form"; +import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; + // TODO: const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => { @@ -151,25 +152,21 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, // 하수맨홀인 경우 용도코드를 텍스트로 변환 if (selectedLayerName === '하수맨홀' && data) { - const processedData = { ...data }; - // mqlt_cd 값 변환 (재질코드) - if (processedData.mqlt_cd) { + if (data.mqlt_cd) { const materialCodeItem = MS_SWAGE_MHL_CODES.find( - code => code.val === processedData.mqlt_cd.trim() + code => code.val === data.mqlt_cd.trim() ); - if (materialCodeItem) { - processedData.mqlt_cd = `${materialCodeItem.text}`; - } + if (materialCodeItem) data.mqlt_cd = `${materialCodeItem.text}`; } - - setfetchData(processedData); - setTableData(processedData); - } else { - setfetchData(data); - setTableData(data); } + // 🎯 기본 데이터 포맷팅 + let formattedData = formatDisplayData(data); + + setfetchData(formattedData); + setTableData(formattedData); + switch (selectedLayerName) { case '하수처리시설': setHeadlist(SwtrmtFcltCol); @@ -185,8 +182,11 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, setButtonChk(false) setIsMainSsc(true) - setfetchData([processedData]) - setTableData(processedData); + // 🎯 processedData도 포맷팅하여 설정 + const processedFormattedData = formatDisplayData(processedData); + + setfetchData([processedFormattedData]) + setTableData(processedFormattedData); setIsLoading(false); // 기본 렌더링 완료 // 수량/수질측정 정보 데이터 호출 diff --git a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx index 12615c87c..2af9cfc19 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -2,11 +2,12 @@ import { deleteData, getData } from '@/components/lib/api' import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' import { z } from 'zod' import BaseLayout from '../BaseLayout' import { handleDelete } from '@/components/lib/utils' +import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ @@ -83,6 +84,11 @@ const Result = () => { const [selectedRowIndex, setSelectedRowIndex] = useState(undefined); const [resetSelection, setResetSelection] = useState(false); + useEffect(() => { + // setCurPage(1); + handleSearchButtonClick(); + }, []) + const handleSearchButtonClick = async (customFormValues?: Partial) => { setIsLoadingResult(true); @@ -92,12 +98,12 @@ const Result = () => { let inftwtAnlsSearchDto; // swageTmplntCd 처리 로직 추가 - const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' + const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' ? (() => { - const valString = String(formValues.swageTmplntCd); - const parts = valString.split('.'); - return parts.length > 1 ? parts[1] : valString; - })() + const valString = String(formValues.swageTmplntCd); + const parts = valString.split('.'); + return parts.length > 1 ? parts[1] : valString; + })() : null; if (customFormValues) { @@ -136,12 +142,20 @@ const Result = () => { console.log("API: ", `v1/rdiiAnls${queryString ? `?${queryString}` : ''}`); try { const data = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`); - // 일련번호를 추가한 데이터로 변환 - const dataWithRowNumber = (data.content || []).map((item: any, index: number) => ({ - ...item, - rowNumber: index + 1, // 1부터 시작하는 일련번호 추가 - scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 - })); + + // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 + const dataWithRowNumber = (data.content || []).map((item: any, index: number) => { + // 🎯 먼저 각 아이템을 포맷팅 + const formattedItem = formatDisplayData(item); + + // 추가 처리 및 일련번호 추가 + return { + ...formattedItem, + rowNumber: index + 1, // 1부터 시작하는 일련번호 추가 + scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 + }; + }); + // setTableData(data.content || []); // setTotalData(tableData.length); setTableData(dataWithRowNumber); @@ -286,9 +300,9 @@ const Result = () => { selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} - setAplcnYmFrom={(val: string) => {form.setValue("aplcnYmFrom", val)}} + setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} - setAplcnYmTo={(val: string) => {form.setValue("aplcnYmTo", val)}} + setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} isLoading={isLoadingResult} // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} diff --git a/apps/sn/app/(with-base-layout)/ii/result/page.tsx b/apps/sn/app/(with-base-layout)/ii/result/page.tsx index 73bc6ecf2..ab8d397e0 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -2,11 +2,12 @@ import { deleteData, getData } from '@/components/lib/api' import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' -import { useState } from 'react' +import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' import { z } from 'zod' import BaseLayout from '../BaseLayout' import { handleDelete } from '@/components/lib/utils' +import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue'; const formSchema = z.object({ @@ -81,6 +82,10 @@ const Result = () => { const [selectedRowIndex, setSelectedRowIndex] = useState(undefined); const [resetSelection, setResetSelection] = useState(false); + useEffect(() => { + // setCurPage(1); + handleSearchButtonClick(); + }, []) const handleSearchButtonClick = async (customFormValues?: Partial) => { setIsLoadingResult(true); @@ -90,12 +95,12 @@ const Result = () => { let inftwtAnlsSearchDto; // swageTmplntCd 처리 로직 추가 - const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' + const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' ? (() => { - const valString = String(formValues.swageTmplntCd); - const parts = valString.split('.'); - return parts.length > 1 ? parts[1] : valString; - })() + const valString = String(formValues.swageTmplntCd); + const parts = valString.split('.'); + return parts.length > 1 ? parts[1] : valString; + })() : null; if (customFormValues) { @@ -136,12 +141,19 @@ const Result = () => { console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); try { const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); - // 일련번호를 추가한 데이터로 변환 - const dataWithRowNumber = (data.content || []).map((item: any, index: number) => ({ - ...item, - rowNumber: index + 1, // 1부터 시작하는 일련번호 추가 - scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 - })); + + // 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가 + const dataWithRowNumber = (data.content || []).map((item: any, index: number) => { + // 🎯 먼저 각 아이템을 포맷팅 + const formattedItem = formatDisplayData(item); + + // 추가 처리 및 일련번호 추가 + return { + ...formattedItem, + rowNumber: index + 1, // 1부터 시작하는 일련번호 추가 + scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경 + }; + }); // setTableData(data.content || []); // setTotalData(tableData.length); setTableData(dataWithRowNumber); @@ -248,7 +260,7 @@ const Result = () => { enoCd: selectedRowData.enoCd || null, ctpvCd: selectedRowData.ctpvCd || null, sggCd: selectedRowData.sggCd || null, - swageTmplntCd: selectedRowData.swageTmplntCd || null, + swageTmplntCd: selectedRowData.swageTmplntCd || null, // swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null, }; await handleSearchButtonClick(customFormValues); @@ -270,6 +282,8 @@ const Result = () => { } }; + + return ( { selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} aplcnYmFrom={form.watch("aplcnYmFrom")} - setAplcnYmFrom={(val: string) => {form.setValue("aplcnYmFrom", val)}} + setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }} aplcnYmTo={form.watch("aplcnYmTo")} - setAplcnYmTo={(val: string) => {form.setValue("aplcnYmTo", val)}} + setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }} isLoading={isLoadingResult} // 로딩 상태 전달 // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} diff --git a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx index 9479a5395..c67c34482 100644 --- a/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/floodPre/page.tsx @@ -1,9 +1,9 @@ 'use client' import { deleteData, postData } from '@/components/lib/api' -import { searchCmmnCd, searchData } from '@/components/lib/common/apiHelpers' -import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils' +import { searchData } from '@/components/lib/common/apiHelpers' +import { formatDate, handleDownload } from '@/components/lib/utils' import LoadingOverlay from '@/components/ui/LoadingBar' -import { SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, UiTable } from '@common/business_components/ui' +import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable, UiModal } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Button, DatePicker, Input, Label } from '@common/components/ui' import { useEffect, useState } from 'react' @@ -15,8 +15,6 @@ const Page = () => { // 조회조건 const [fromDate, setFromDate] = useState(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date }) const [toDate, setToDate] = useState(new Date()) - const [manageYear, setManageYear] = useState(yearOptions[0]?.val || ''); - const [mngQu, setMngQu] = useState(yearOptions[0]?.val || ''); const [usr, setUsr] = useState('') const [pjNm, setpjNm] = useState('') // 파일업로드 관련 상태 @@ -27,6 +25,7 @@ const Page = () => { const [curPage, setCurPage] = useState(1) //현재 페이지 const [totalData, setTotalData] = useState(0) //총 데이터 수 const [isLoading, setIsLoading] = useState(true) //로딩 상태 + const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태 // 조회 const fetchData = async () => { @@ -87,6 +86,7 @@ const Page = () => { } } } + // 현황 등록 const registFile = async (fileName: string, year: string, quater: string, cd: string, id: string) => { const data = await postData('v1/sbmrgPrevnt', { @@ -97,8 +97,19 @@ const Page = () => { atflId: id, sbmrgPrevntBizNm: fileName }) - if (data) { alert('파일이 등록되었습니다.'); fetchData() } + if (data) { + alert('파일이 등록되었습니다.'); + setIsModalOpen(false); + fetchData(); + } } + + // 등록 버튼 클릭 핸들러 + const handleRegisterClick = () => { + setIsModalOpen(true); + } + + return ( <> @@ -142,7 +153,10 @@ const Page = () => { - +
+ + +
@@ -171,9 +185,22 @@ const Page = () => { onPageChange={async (page: number) => { setCurPage(page); await fetchData() }} /> - - { await registFile(file, year, quater, cd, id) }} /> - + {/* 파일 업로드 모달 */} + { + await registFile(file, year, quater, cd, id) + }} + /> + } + /> ) } diff --git a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx index 5236e1c17..fb1725f55 100644 --- a/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx +++ b/apps/sn/app/(with-base-layout)/oper/oldPp/page.tsx @@ -3,7 +3,7 @@ import { deleteData, postData } from '@/components/lib/api' import { searchData } from '@/components/lib/common/apiHelpers' import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils' import LoadingOverlay from '@/components/ui/LoadingBar' -import { SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, UiTable } from '@common/business_components/ui' +import { SearchDiv, SearchFormLeft, SearchFormRight, UiTable, UiModal } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Button, DatePicker, Input, Label } from '@common/components/ui' import { useEffect, useState } from 'react' @@ -14,8 +14,6 @@ import OperFileUploadForm from '../../../components/ui/operFileUploadForm' const Page = () => { const [fromDate, setFromDate] = useState(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date }); const [toDate, setToDate] = useState(new Date()); - const [manageYear, setManageYear] = useState(yearOptions[0]?.val || ''); - const [mngQu, setMngQu] = useState(yearOptions[0]?.val || ''); const [usr, setUsr] = useState('') const [pjNm, setpjNm] = useState('') const [fileNm, setFileNm] = useState('') @@ -25,6 +23,7 @@ const Page = () => { const [curPage, setCurPage] = useState(1) const [totalData, setTotalData] = useState(0) //총 데이터 수 const [isLoading, setIsLoading] = useState(true) //로딩 상태 + const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태 // 데이터 조회 const fetchData = async () => { @@ -98,7 +97,16 @@ const Page = () => { atflId: id, dcrpPplMtncBizNm: fileNm }) - if (data) { alert('파일이 등록되었습니다.'); fetchData() } + if (data) { + alert('파일이 등록되었습니다.'); + setIsModalOpen(false); + fetchData(); + } + } + + // 등록 버튼 클릭 핸들러 + const handleRegisterClick = () => { + setIsModalOpen(true); } @@ -143,7 +151,10 @@ const Page = () => { - +
+ + +
@@ -173,9 +184,22 @@ const Page = () => { onPageChange={async (page: number) => { setCurPage(page); await fetchData() }} /> - - { await registFile(file, year, quater, cd, id) }} /> - + {/* 파일 업로드 모달 */} + { + await registFile(file, year, quater, cd, id) + }} + /> + } + /> ) } diff --git a/apps/sn/app/components/lib/utils/date.ts b/apps/sn/app/components/lib/utils/date.ts index 9c9106ec2..a401ad738 100644 --- a/apps/sn/app/components/lib/utils/date.ts +++ b/apps/sn/app/components/lib/utils/date.ts @@ -54,4 +54,57 @@ export const convertToTextValArray = (yyyymms: string[]): { text: string; val: s val: dateStr }; }); +}; + +// 🎯 날짜 유효성 검사 함수 (8자리 또는 6자리 지원) +export const isValidDateString = (dateStr: string): boolean => { + // 숫자만 추출 + const cleanStr = dateStr.replace(/\D/g, ''); + + // 8자리 (yyyymmdd) 또는 6자리 (yyyymm) 확인 + if (cleanStr.length === 8) { + // 연도, 월, 일 추출 + const year = parseInt(cleanStr.substring(0, 4)); + const month = parseInt(cleanStr.substring(4, 6)); + const day = parseInt(cleanStr.substring(6, 8)); + + // 기본 범위 검사 + if (year < 1900 || year > 2200) return false; + if (month < 1 || month > 12) return false; + if (day < 1 || day > 31) return false; + + // Date 객체로 실제 유효한 날짜인지 검사 + const date = new Date(year, month - 1, day); + return date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day; + } + else if (cleanStr.length === 6) { + // 연도, 월 추출 + const year = parseInt(cleanStr.substring(0, 4)); + const month = parseInt(cleanStr.substring(4, 6)); + + // 기본 범위 검사 + if (year < 1900 || year > 2200) return false; + if (month < 1 || month > 12) return false; + + return true; + } + + return false; +}; + +// 🎯 날짜 포맷팅 함수 +export const formatDateString = (dateStr: string): string => { + const cleanStr = dateStr.replace(/\D/g, ''); // 숫자만 추출 + + if (!isValidDateString(cleanStr)) { + return dateStr; // 유효하지 않은 날짜면 원본 반환 + } + + const year = cleanStr.substring(0, 4); + const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거 + const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거 + + return `${year}년 ${month}월 ${day}일`; }; \ No newline at end of file diff --git a/apps/sn/app/components/lib/utils/formatDisplayValue.tsx b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx new file mode 100644 index 000000000..858453291 --- /dev/null +++ b/apps/sn/app/components/lib/utils/formatDisplayValue.tsx @@ -0,0 +1,96 @@ +import { dateFields, numberFields, yearMonthFields, dateTimeFields } from "../../../(map-layout)/map/components/common/table"; +import { isValidDateString, formatDateString } from "./date"; + +// 🎯 데이터 객체 전체를 포맷팅하는 함수 +export const formatDisplayData = (data: { [key: string]: any }): { [key: string]: string } => { + if (!data) return {}; + + const formattedData: { [key: string]: string } = {}; + + Object.keys(data).forEach(fieldId => { + const value = data[fieldId]; + + if (value === null || value === undefined || value === "") { + formattedData[fieldId] = "-"; + return; + } + + // 🎯 연월 포맷팅 (yyyymm -> yyyy년 m월) + if (yearMonthFields.includes(fieldId)) { + const dateStr = String(value).trim(); + if (/^\d{6}$/.test(dateStr)) { + const year = dateStr.substring(0, 4); + const month = parseInt(dateStr.substring(4, 6)); + formattedData[fieldId] = `${year}년 ${month}월`; + } else { + formattedData[fieldId] = String(value); + } + return; + } + + // 🎯 날짜시간 포맷팅 (yyyy-mm-dd hh:mm:ss -> yyyy년 m월 d일 hh:mm) + if (dateTimeFields.includes(fieldId)) { + const dateTimeStr = String(value).trim(); + // yyyy-mm-dd hh:mm:ss 형식 확인 + const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/; + const match = dateTimeStr.match(dateTimeRegex); + + if (match && match.length >= 6) { + const year = match[1]; + const month = match[2]; + const day = match[3]; + const hour = match[4]; + const minute = match[5]; + const second = match[6]; + + // 🎯 undefined 체크 후 parseInt 적용 + if (year && month && day && hour && minute) { + const formattedMonth = parseInt(month); + const formattedDay = parseInt(day); + formattedData[fieldId] = `${year}년 ${formattedMonth}월 ${formattedDay}일 ${hour}시 ${minute}분 ${second}초`; + } else { + formattedData[fieldId] = String(value); + } + } else { + formattedData[fieldId] = String(value); + } + return; + } + + // 🎯 기존 날짜 포맷팅 (yyyymmdd -> yyyy년 m월 d일) + if (dateFields.includes(fieldId)) { + const dateStr = String(value).trim(); + + // 숫자 타입이거나 숫자로만 구성된 문자열인지 확인 + if (/^\d+$/.test(dateStr)) { + formattedData[fieldId] = formatDateString(dateStr); + } else { + formattedData[fieldId] = String(value); + } + return; + } + + // 🎯 숫자 필드는 천 단위 구분자 추가 + if (numberFields.includes(fieldId)) { + const numValue = Number(value); + if (!isNaN(numValue) && isFinite(numValue)) { + formattedData[fieldId] = numValue.toLocaleString('ko-KR'); + } else { + formattedData[fieldId] = String(value); + } + return; + } + + // 나머지는 문자열로 처리 + formattedData[fieldId] = String(value); + }); + + return formattedData; +}; + +// 🎯 단일 값만 포맷팅하는 함수 (기존 호환성을 위해 유지) +export const formatDisplayValue = (value: any, fieldId: string): string => { + const tempData = { [fieldId]: value }; + const formatted = formatDisplayData(tempData); + return formatted[fieldId] || String(value); +}; \ No newline at end of file diff --git a/apps/sn/app/components/lib/utils/index.tsx b/apps/sn/app/components/lib/utils/index.tsx index 272ca7ada..8bc8cf9be 100644 --- a/apps/sn/app/components/lib/utils/index.tsx +++ b/apps/sn/app/components/lib/utils/index.tsx @@ -4,3 +4,4 @@ export * from './date' export * from './excelToJson' export * from './fileDeleteUtil' export * from './fileDownload' +export * from './formatDisplayValue' diff --git a/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx deleted file mode 100644 index 1fc0cef80..000000000 --- a/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { VerticalTable } from '../../../components/ui/verticalTable'; -import React, { useState } from 'react' -import InputSection from '../InputSection'; -import { handleInputChange } from '../common'; -import { Button } from '@common/components/ui'; -import InfilChart from './chart/infilChart'; -// 하수관로 -// TODO: RDII 버튼인가 그거 팝업 두개 구현 필요 -const inputColumn = [ - { id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] }, - { id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, - { id: "basu", title: "배수구역", type: "input" }, - { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, - { id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] }, - { id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] }, - { id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] }, -]; -const SwtrmtFclt = ({ featureData }: { featureData: any }) => { - console.log("featureData", featureData); - const [isModalOpen, setIsModalOpen] = React.useState(false); - const [btnTxt, setBtnTxt] = useState(""); - const openModal = (e: React.MouseEvent) => { - e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent) - setIsModalOpen(true); - }; - const closeModal = () => { - setIsModalOpen(false); - }; - const [repairData, setRepairData] = useState([ - { year: "2025", type: "개대체" }, - { year: "2025", type: "보수" }, - ]); - const [cleaningData, setCleaningData] = useState([ - { year: "2025", type: "청소" }, - { year: "2025", type: "준설" }, - ]); - const [inspectionData, setInspectionData] = useState([ - { year: "2025", type: "관로기술진단" }, - { year: "2025", type: "관로기술진단" }, - ]); - const dataMap = { - repair: { data: repairData, setData: setRepairData }, - cleaning: { data: cleaningData, setData: setCleaningData }, - inspection: { data: inspectionData, setData: setInspectionData }, - }; - const keysToExtract = [ - "swtrmt_zone_cd", - "mltv_dtdv_nm", - "fid", - "lyrTypeNm", - "reg_dt", - "mdfcn_dt", - "rgtr", - "mdfr", - ]; - // const filteredData = Object.entries(featureData) - // .filter(([key]) => keysToExtract.includes(key)) // 필요한 키만 필터링 - // .map(([key, value]) => ({ key, value: value ?? "N/A" })); // 테이블 데이터 형식으로 변환 - - return ( -
- - handleInputChange(section, index, field, value, dataMap)} /> - handleInputChange(section, index, field, value, dataMap)} /> - handleInputChange(section, index, field, value, dataMap)} /> -
- - -
- -
- ) -} - -export default SwtrmtFclt \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx deleted file mode 100644 index 0d1c3ef2e..000000000 --- a/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import Modal from '../../../components/ui/modal'; -import { VerticalTable } from '../../../components/ui/verticalTable'; -import { UiTable } from '@common/business_components/ui'; -import { Button, TableCell } from '@common/components/ui'; -import React, { useState } from 'react' - -// 하수처리시설 -const headList = [ - { id: "title", title: "하수도정비 기본계획보고서" }, - { id: "cad", title: "하수도대장 평면도" },] -const modalHeadList = [ - { id: "no", title: "번호" }, - { id: "year", title: "승인연도" }, - { id: "cad", title: '평면도'}, - { id: "fileNm", title: "파일명" }, - { id: "date", title: "입력시간" }, -] - -const SwtrmtFclt = () => { - const [isModalOpen, setIsModalOpen] = useState(false); - const [modalTitle, setModalTitle] = useState(''); - const openModal = (title:string) => { - setIsModalOpen(true); - setModalTitle(title) - }; - const closeModal = () => { - setIsModalOpen(false); - }; - return ( -
- openModal('하수도정비 기본계획보고서')}>보고서, - cad: , - }} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void { - throw new Error("Function not implemented."); - }}> - -
- -
-
-
- ) -} - -export default SwtrmtFclt \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx deleted file mode 100644 index 06c5b5fec..000000000 --- a/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useState } from 'react' -import { type ChartConfig, MainChart, CHART_FORM } from "@common/business_components"; -import Modal from '../../../../components/ui/modal'; -import { Button } from '@common/components/ui'; -type ModalOpenProp = { - isModalOpen: boolean, - setIsModalOpen: (open: boolean) => void, - closeModal: () => void, - btnTxt?: string, -} -const InfilChart = ({ btnTxt, isModalOpen, setIsModalOpen, closeModal }: ModalOpenProp) => { - const [popupOpen, setPopupOpen] = React.useState(false); - const buttonRef = React.useRef(null); // 버튼 참조 - const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const openPopup = () => { - if (buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 - setPopupPosition({ - top: rect.bottom + window.scrollY, // 버튼 아래쪽 - left: rect.left + window.scrollX, // 버튼 왼쪽 - }); - } - setPopupOpen(true); // 팝업 열기 - }; - console.log("btnTxt", btnTxt); - const closePopup = () => {setPopupOpen(false);}; // 팝업 닫기 - const chartForm = CHART_FORM.LINE; - const chartData = { - chartData1: [ - { label: "1", val: 20 }, - { label: "3", val: 40 }, - { label: "5", val: 60 }, - ], - chartData2: [ - { label: "1", val: 15 }, - { label: "3", val: 35 }, - { label: "5", val: 55 }, - ], - } - const chartConfig = Object.values(chartData).flat().reduce((config, element) => { - config[element.label] = { - label: element.label, - color: "hsl(var(--chart-1))", - }; - return config; - }, {} as Record) satisfies ChartConfig; - const getCurrentDate = () => { - const today = new Date(); - const year = today.getFullYear(); - const month = String(today.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 +1 - return `${year}-${month}`; - }; - return ( - - {/* 분석 입력현황 조회 */} -
-
- - - {/* 침입수량 분석결과 */} -
- 침입수량 분석결과 -
- 120 - m³/d -
-
-
- {/* 차트 */} -
- -
-
- {popupOpen && ( -
-
분석 입력현황
-
-
상수사용량 정보:
-
- -
-
오수 전환율:
-
- -
-
수용가-맨홀 매칭:
-
- -
-
관망도 자료:
-
- -
- {btnTxt === 'RDII' ? <>
강우 자료:
-
- -
-
RTX 계수 출처:
-
- -
- : null} -
-
- - -
-
- )} -
- ) -} - -export default InfilChart \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/map/components/popupComponent/sewagePump.tsx deleted file mode 100644 index 79103a40e..000000000 --- a/apps/sn/app/map/components/popupComponent/sewagePump.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { VerticalTable } from '../../../components/ui/verticalTable'; -import React from 'react' -const inputColumn = [ - { id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, - { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, - { id: "freq", title: "설계빈도", type: "input" }, -] -// 오수펌프 -const sewagePump = ({ featureData }: { featureData: any }) => { - return ( -
-
- ) -} - -export default sewagePump diff --git a/apps/sn/app/map/components/popupComponent/smh.tsx b/apps/sn/app/map/components/popupComponent/smh.tsx deleted file mode 100644 index 39920232b..000000000 --- a/apps/sn/app/map/components/popupComponent/smh.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// 하수맨홀 -import { VerticalTable } from '../../../components/ui/verticalTable'; -import React from 'react' -const inputColumn = [ - { id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, - { id: "basu", title: "배수구역", type: "input" }, - { id: "bungu", title: "배수분구", type: "input" }, - { id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] }, - {id:'spec', title:'규격', type:'input'}, -]; -const Smh = () => { - return ( -
- -
- ) -} - -export default Smh; \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx b/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx deleted file mode 100644 index 172022f31..000000000 --- a/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { VerticalTable } from '../../../components/ui/verticalTable'; -import React from 'react' -const inputColumn = [ - { id: "swtrmt_zone_cd", title: "처리구역", type: "input" }, - { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, -] -// 빗물펌프 -const StormWaterPump = ({ featureData }: { featureData: Record }) => { - const validKey = inputColumn.map((item) => item.id); - const filteredData = featureData && Object.entries(featureData).filter(([key]) => validKey.includes(key)).reduce((obj: Record, [key, value]) => {obj[key] = value; return obj;}, {}); - return ( -
-
- ) -} - -export default StormWaterPump \ No newline at end of file