Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/sn/dev

This commit is contained in:
yschoi
2025-07-14 13:04:47 +09:00
10 changed files with 331 additions and 64 deletions
@@ -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'
];
@@ -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<any>({});
// featureData가 변경될 때 formData 초기화
@@ -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); // 기본 렌더링 완료
// 수량/수질측정 정보 데이터 호출
@@ -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<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
useEffect(() => {
// setCurPage(1);
handleSearchButtonClick();
}, [])
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
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}
@@ -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<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
useEffect(() => {
// setCurPage(1);
handleSearchButtonClick();
}, [])
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
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 (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
@@ -289,9 +303,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}
@@ -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 (
<>
<LoadingOverlay isLoading={isLoading ?? false} />
@@ -142,7 +153,10 @@ const Page = () => {
</div>
</SearchFormLeft>
<SearchFormRight>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await fetchData()} >조회</Button>
<div className='flex items-center gap-5'>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await fetchData()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={handleRegisterClick} >등록</Button>
</div>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
@@ -171,9 +185,22 @@ const Page = () => {
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
/>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='도시침수 예방사업 사업추진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
{/* 파일 업로드 모달 */}
<UiModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
bodyClass="w-auto max-w-[90vw] min-w-[600px] h-auto"
noHeaderYn="N"
noFooterYn="Y"
body={
<OperFileUploadForm
title='도시침수 예방사업 추진현황 등록'
onSubmit={async (file, year, quater, cd, id) => {
await registFile(file, year, quater, cd, id)
}}
/>
}
/>
</>
)
}
@@ -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<Date>(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date });
const [toDate, setToDate] = useState<Date>(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 = () => {
</div>
</SearchFormLeft>
<SearchFormRight>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 mr-2" size="sm" onClick={async () => { await fetchData() }}>조회</Button>
<div className='flex items-center gap-5'>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await fetchData()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={handleRegisterClick} >등록</Button>
</div>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
@@ -173,9 +184,22 @@ const Page = () => {
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
/>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='노후 하수관로 정비사업 주진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
{/* 파일 업로드 모달 */}
<UiModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
bodyClass="w-auto max-w-[90vw] min-w-[600px] h-auto"
noHeaderYn="N"
noFooterYn="Y"
body={
<OperFileUploadForm
title='노후 하수관로 정비사업 추진현황 등록'
onSubmit={async (file, year, quater, cd, id) => {
await registFile(file, year, quater, cd, id)
}}
/>
}
/>
</>
)
}
+53
View File
@@ -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}일`;
};
@@ -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);
};
@@ -4,3 +4,4 @@ export * from './date'
export * from './excelToJson'
export * from './fileDeleteUtil'
export * from './fileDownload'
export * from './formatDisplayValue'