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'