diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index f9596613f..86ae34f46 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -2,7 +2,7 @@ import { getData } from '@/components/lib/api' import ContentLayout from '@/components/ui/contentLayout' import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm' -import { DatePicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui' +import { DatePicker, DateMonthPicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { SelectBox2 } from '@common/business_components/ui/select/index-2' import { Button, Label } from '@common/components/ui' @@ -51,8 +51,10 @@ type BaseLayout = { onSwageTreatmentPlantChange?: (value: string | number) => void; selectedSwageProcessingZone?: string | number; onSwageProcessingZoneChange?: (value: string | number) => void; - aplcnYm?: Date - setAplcnYm?: (aplcnYm: Date | undefined) => void; + aplcnYmFrom?: string + setAplcnYmFrom?: (aplcnYmFrom: string) => void; + aplcnYmTo?: string + setAplcnYmTo?: (aplcnYmTo: string) => void; isLoading?: boolean; // Excel 다운로드 관련 props 추가 onExcelDownload?: () => void; @@ -89,8 +91,10 @@ const BaseLayout = ({ onSwageTreatmentPlantChange, selectedSwageProcessingZone, onSwageProcessingZoneChange, - aplcnYm, - setAplcnYm, + aplcnYmFrom, + setAplcnYmFrom, + aplcnYmTo, + setAplcnYmTo, isLoading, // Excel 다운로드 관련 props 추가 onExcelDownload, @@ -99,7 +103,7 @@ const BaseLayout = ({ selectedRowData }: BaseLayout) => { - const defaultOption: ApiSelectOption = { val: '0', text: '선택' }; + const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; const [processingZoneOptions, setProcessingZoneOptions] = useState([]); const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState([]); @@ -216,13 +220,37 @@ const BaseLayout = ({ {showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링 <> - console.log("DatePicker onChange:", date)) + value: aplcnYmFrom || (() => { // aplcnYm이 없으면 현재 연월을 기본값으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), // yyyyMM 형식 + onChange: (value: string) => { + setAplcnYmFrom?.(value); // string 값을 직접 전달 + } }} - selectClass="ml-5 date-picker-trigger" /> + selectClass="ml-5 w-[100px]" + startYear={1900} // 시작 연도 설정 + /> + ~ + { // aplcnYm이 없으면 현재 연월을 기본값으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), // yyyyMM 형식 + onChange: (value: string) => { + setAplcnYmTo?.(value); // string 값을 직접 전달 + } + }} + selectClass="ml-5 w-[100px]" + startYear={1900} // 시작 연도 설정 + /> )} { initialFileInfo // 3: 유량자료 등록 ]); - const [selectedDate, setSelectedDate] = useState(new Date()); + // selectedDate를 yyyyMM 문자열 형식으로 변경 + const [selectedDate, setSelectedDate] = useState(() => { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + }); const [input1, setInput1] = useState(''); const [input2, setInput2] = useState(''); const [select1, setSelect1] = useState('0'); // 환경청코드 @@ -87,7 +93,11 @@ const IrSetting = () => { initialFileInfo, initialFileInfo ]); - setSelectedDate(new Date()); // 날짜 선택 초기화 + // selectedDate를 현재 연월로 초기화 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + setSelectedDate(`${year}${month}`); setInput1(''); // 입력 필드 초기화 setInput2(''); // 입력 필드 초기화 setSelect1('0'); // 선택 필드 초기화 @@ -138,22 +148,6 @@ const IrSetting = () => { const handleAnalysisClick = async () => { setIsLoadingResult(true); // 로딩 상태 시작 - // 오수전환율 값 0 ~ 1 사이인지 검증 - if (input1 === '' || input1 === null || input1 === undefined) { - setIsLoadingResult(false); - console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - alert('오수전환율을 입력해주세요.'); - return; - } - - const numericInput1 = parseFloat(input1); - if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) { - setIsLoadingResult(false); - console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - alert('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - return; - } - console.log(uploadedFilesData); const file1 = uploadedFilesData[0]; // 오수관망도 const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과 @@ -173,6 +167,22 @@ const IrSetting = () => { return; } + // 오수전환율 값 0 ~ 1 사이인지 검증 + if (input1 === '' || input1 === null || input1 === undefined) { + setIsLoadingResult(false); + console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + alert('오수전환율을 입력해주세요.'); + return; + } + + const numericInput1 = parseFloat(input1); + if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) { + setIsLoadingResult(false); + console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + alert('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + return; + } + type MatchingResult = { anlsSn: number; mhlMngNo: string; rcptrNo: string; }; type TimePattern = { hrId: number; rt?: number | null | undefined; }; type FlowRate = { pplMngNo: string; mtrngDt: string; oilq?: number | null | undefined; }; @@ -236,7 +246,7 @@ const IrSetting = () => { swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0) ? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용 : null, // 하수처리구역코드 (선택값) - aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월 + aplcnYm: selectedDate, // 적용연월 drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환) anlsInfo: input2, // 분석정보 atflCd: file1?.atflCd, // 첨부파일CD (오수관망도) @@ -316,19 +326,17 @@ const IrSetting = () => { - + {/* 연월 선택 라이브러리로 수정 필요 */} + { - if (date) { - setSelectedDate(date); - form.setValue('inputdate', date, { shouldValidate: true }); - } + value: selectedDate, // yyyyMM 형식 + onChange: (value: string) => { + setSelectedDate(value); } }} - selectClass="ml-5 date-picker-trigger" // date-picker.css의 스타일 적용 + selectClass="ml-5 w-[150px]" + startYear={1900} // 시작 연도 설정 /> { // 현재 연월을 yyyyMM 형식으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), }; // 각 파일 업로드 폼의 데이터를 저장할 인터페이스 정의 @@ -108,7 +113,6 @@ export default function RTKForm() { const [select4, setSelect4] = useState('0'); // 하수처리장 (swageTmplntCd) const [select5, setSelect5] = useState('0'); // 하수처리구역 (swtrmtZoneCd) - // form 인스턴스 생성 const form = useForm({ resolver: zodResolver(formSchema), @@ -163,23 +167,6 @@ export default function RTKForm() { // 침입수 분석 실행 버튼 클릭 핸들러 const handleAnalysisClick = async () => { setIsLoadingResult(true); // 로딩 상태 시작 - - // 오수전환율 값 0 ~ 1 사이인지 검증 - const rateValue = form.getValues().rate; - if (rateValue === '' || rateValue === null || rateValue === undefined) { - setIsLoadingResult(false); - console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - alert('오수전환율을 입력해주세요.'); - return; - } - - const numericInput1 = parseFloat(rateValue); - if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) { - setIsLoadingResult(false); - console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - alert('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); - return; - } const formData = form.getValues(); console.log(uploadedFilesData); @@ -198,6 +185,23 @@ export default function RTKForm() { return; } + // 오수전환율 값 0 ~ 1 사이인지 검증 + const rateValue = form.getValues().rate; + if (rateValue === '' || rateValue === null || rateValue === undefined) { + setIsLoadingResult(false); + console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + alert('오수전환율을 입력해주세요.'); + return; + } + + const numericInput1 = parseFloat(rateValue); + if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) { + setIsLoadingResult(false); + console.error('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + alert('오수전환율은 0에서 1 사이의 값으로 입력해야 합니다.'); + return; + } + const filePickers = [ { file: file2, parser: excelToMatchingResultData }, { file: file3, parser: excelToRainData }, @@ -241,7 +245,7 @@ export default function RTKForm() { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값) - aplcnYm: new Date(formData.date).toISOString().substring(0, 7).replace("-", ""), // 적용연월 (YYYYMM 형식) + aplcnYm: formData.date, // 적용연월 (YYYYMM 형식) ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값) sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값) swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0) @@ -337,6 +341,35 @@ export default function RTKForm() { console.log('Page changed:', page) } + // 선택된 값을 처리하는 공통 함수 + const handleSelectValueChange = ( + val: string | number, + setter: (value: string | number) => void + ) => { + // val이 0이 아닐 경우 .을 기준으로 슬라이싱해서 두번째 값을 설정 + if (val && val !== '0' && val !== 0) { + const valString = String(val); + const parts = valString.split('.'); + if (parts.length > 1 && parts[1] !== undefined) { + setter(parts[1]); // 두 번째 값 (인덱스 1) + } else { + setter(val); // .이 없으면 원래 값 사용 + } + } else { + setter('0'); // val이 0이면 '0' 설정 + } + }; + + // 하수처리장 변경 핸들러 + const handleSwageTreatmentPlantChange = (val: string | number) => { + handleSelectValueChange(val, setSelect4); + }; + + // 하수처리구역 변경 핸들러 + const handleSwageProcessingZoneChange = (val: string | number) => { + handleSelectValueChange(val, setSelect5); + }; + return ( // { }} graphBtn={false} > @@ -447,30 +480,16 @@ export default function RTKForm() { placeholder="예: 0.75" /> - - {/* { - if (date) { - form.setValue("date", date, { shouldValidate: true }); - } - }} - mode={"single"} - className="ml-5" - /> */} - + { - if (date) { - form.setValue("date", date, { shouldValidate: true }); - } + value: form.watch("date"), // form의 date 값을 직접 사용 + onChange: (value: string) => { + form.setValue("date", value, { shouldValidate: true }); // form의 date 필드에 직접 설정 } }} - // className을 직접 사용하거나, DatePicker 내부에서 selectClass를 처리하도록 수정 필요 - // 여기서는 DatePicker의 selectClass prop을 사용하고, date-picker.css의 스타일을 적용 - selectClass="ml-5 date-picker-trigger" + selectClass="ml-5 w-[200px]" + startYear={1900} // 시작 연도 설정 /> 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 bec7ffec6..12615c87c 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx @@ -18,12 +18,24 @@ const formSchema = z.object({ }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 - aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동) + aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + }), // 분석기간 시작연월 + aplcnYmTo: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + }), // 분석기간 종료연월 }) type ResultFormValues = z.infer const tableColumn = [ + { id: 'rowNumber', title: '일련번호' }, { id: 'enoNm', title: '환경청' }, { id: 'ctpvNm', title: '시도' }, { id: 'sggNm', title: '시군구' }, @@ -35,7 +47,8 @@ const tableColumn = [ { id: 'cntbArea', title: 'RDII 기여면적' }, { id: 'anlsInfo', title: 'RDII 분석정보' }, { id: 'regDt', title: '분석일자' }, - { id: 'rgtrNm', title: '분석자' } + { id: 'rgtrNm', title: '분석자' }, + { id: 'scsYn', title: '분석성공여부' } ] const Result = () => { @@ -47,7 +60,18 @@ const Result = () => { sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', - aplcnYm: new Date(), + aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), + aplcnYmTo: (() => { // 현재 연월을 yyyyMM 형식으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), } }) @@ -67,21 +91,32 @@ const Result = () => { let inftwtAnlsSearchDto; + // swageTmplntCd 처리 로직 추가 + const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' + ? (() => { + const valString = String(formValues.swageTmplntCd); + const parts = valString.split('.'); + return parts.length > 1 ? parts[1] : valString; + })() + : null; + if (customFormValues) { inftwtAnlsSearchDto = { - "aplcnYm": formValues.aplcnYm, + "aplcnYmFrom": formValues.aplcnYmFrom, + "aplcnYmTo": formValues.aplcnYmTo, "enoCd": formValues.enoCd, "ctpvCd": formValues.ctpvCd, "sggCd": formValues.sggCd, - "swtrmtZoneCd": formValues.swtrmtZoneCd, + "swageTmplntCd": processedSwageTmplntCd, }; } else { inftwtAnlsSearchDto = { - "aplcnYm": formValues.aplcnYm ? formValues.aplcnYm.toISOString().substring(0, 7).replace("-", "") : null, + "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, + "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, - "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd, + "swageTmplntCd": processedSwageTmplntCd, }; } console.log("Search DTO:", inftwtAnlsSearchDto); @@ -101,10 +136,18 @@ const Result = () => { console.log("API: ", `v1/rdiiAnls${queryString ? `?${queryString}` : ''}`); try { const data = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`); - setTableData(data.content || []); - setTotalData(tableData.length); + // 일련번호를 추가한 데이터로 변환 + 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'으로 변경 + })); + // setTableData(data.content || []); + // setTotalData(tableData.length); + setTableData(dataWithRowNumber); + setTotalData(dataWithRowNumber.length); console.log("response:", data); - console.log("response data:", data.content || []); + console.log("response data:", dataWithRowNumber); setIsLoadingResult(false); } catch (error) { setIsLoadingResult(false); @@ -198,11 +241,12 @@ const Result = () => { // 삭제 후 테이블 새로고침을 위해 재검색 const customFormValues: Partial = { - aplcnYm: selectedRowData.aplcnYm || null, + aplcnYmFrom: selectedRowData.aplcnYmFrom || null, + aplcnYmTo: selectedRowData.aplcnYmTo || null, enoCd: selectedRowData.enoCd || null, ctpvCd: selectedRowData.ctpvCd || null, sggCd: selectedRowData.sggCd || null, - swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null, + swageTmplntCd: selectedRowData.swageTmplntCd || null, }; await handleSearchButtonClick(customFormValues); @@ -241,14 +285,10 @@ const Result = () => { onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} - aplcnYm={form.watch("aplcnYm")} - setAplcnYm={(val) => { - if (val instanceof Date) { - form.setValue("aplcnYm", val) - } else if (val) { - form.setValue("aplcnYm", new Date(val)) - } - }} + aplcnYmFrom={form.watch("aplcnYmFrom")} + setAplcnYmFrom={(val: string) => {form.setValue("aplcnYmFrom", val)}} + aplcnYmTo={form.watch("aplcnYmTo")} + 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 988521eca..73bc6ecf2 100644 --- a/apps/sn/app/(with-base-layout)/ii/result/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/result/page.tsx @@ -1,6 +1,6 @@ 'use client' import { deleteData, getData } from '@/components/lib/api' -import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' +import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState } from 'react' import { useForm } from 'react-hook-form' @@ -18,12 +18,24 @@ const formSchema = z.object({ }).default({ val: '0', text: '전체' }), swageTmplntCd: z.string().default('0'), // 하수처리장 swtrmtZoneCd: z.string().default('0'), // 하수처리구역 - aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동) + aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + }), // 분석기간 시작연월 + aplcnYmTo: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + }), // 분석기간 종료연월 }) type ResultFormValues = z.infer const tableColumn = [ + { id: 'rowNumber', title: '일련번호' }, { id: 'enoNm', title: '환경청' }, { id: 'ctpvNm', title: '시도' }, { id: 'sggNm', title: '시군구' }, @@ -33,7 +45,8 @@ const tableColumn = [ { id: 'drtwSwtcRt', title: '오수 전환율' }, { id: 'anlsInfo', title: '침입수 분석정보' }, { id: 'regDt', title: '분석일자' }, - { id: 'rgtrNm', title: '분석자' } + { id: 'rgtrNm', title: '분석자' }, + { id: 'scsYn', title: '분석성공여부' } ] const Result = () => { @@ -45,7 +58,18 @@ const Result = () => { sggCd: { val: '0', text: '전체' }, swageTmplntCd: '0', swtrmtZoneCd: '0', - aplcnYm: new Date(), + aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), + aplcnYmTo: (() => { // 현재 연월을 yyyyMM 형식으로 설정 + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${year}${month}`; + })(), } }) @@ -65,21 +89,34 @@ const Result = () => { let inftwtAnlsSearchDto; + // swageTmplntCd 처리 로직 추가 + const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0' + ? (() => { + const valString = String(formValues.swageTmplntCd); + const parts = valString.split('.'); + return parts.length > 1 ? parts[1] : valString; + })() + : null; + if (customFormValues) { inftwtAnlsSearchDto = { - "aplcnYm": formValues.aplcnYm, + "aplcnYmFrom": formValues.aplcnYmFrom, + "aplcnYmTo": formValues.aplcnYmTo, "enoCd": formValues.enoCd, "ctpvCd": formValues.ctpvCd, "sggCd": formValues.sggCd, - "swtrmtZoneCd": formValues.swtrmtZoneCd, + "swageTmplntCd": processedSwageTmplntCd, + // "swtrmtZoneCd": formValues.swtrmtZoneCd, }; } else { inftwtAnlsSearchDto = { - "aplcnYm": formValues.aplcnYm ? formValues.aplcnYm.toISOString().substring(0, 7).replace("-", "") : null, + "aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null, + "aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null, "enoCd": formValues.enoCd === '0' ? null : formValues.enoCd, "ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd, "sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val, - "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd, + "swageTmplntCd": processedSwageTmplntCd, + // "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd, }; } console.log("Search DTO:", inftwtAnlsSearchDto); @@ -99,10 +136,18 @@ const Result = () => { console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); try { const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); - setTableData(data.content || []); - setTotalData(tableData.length); + // 일련번호를 추가한 데이터로 변환 + 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'으로 변경 + })); + // setTableData(data.content || []); + // setTotalData(tableData.length); + setTableData(dataWithRowNumber); + setTotalData(dataWithRowNumber.length); console.log("response:", data); - console.log("response data:", data.content || []); + console.log("response data:", dataWithRowNumber); setIsLoadingResult(false); } catch (error) { setIsLoadingResult(false); @@ -198,11 +243,13 @@ const Result = () => { // 삭제 후 테이블 새로고침을 위해 재검색 const customFormValues: Partial = { - aplcnYm: selectedRowData.aplcnYm || null, + aplcnYmFrom: selectedRowData.aplcnYmFrom || null, + aplcnYmTo: selectedRowData.aplcnYmTo || null, enoCd: selectedRowData.enoCd || null, ctpvCd: selectedRowData.ctpvCd || null, sggCd: selectedRowData.sggCd || null, - swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null, + swageTmplntCd: selectedRowData.swageTmplntCd || null, + // swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null, }; await handleSearchButtonClick(customFormValues); @@ -241,14 +288,10 @@ const Result = () => { onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))} selectedSwageProcessingZone={form.watch("swtrmtZoneCd")} onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))} - aplcnYm={form.watch("aplcnYm")} - setAplcnYm={(val) => { - if (val instanceof Date) { - form.setValue("aplcnYm", val) - } else if (val) { - form.setValue("aplcnYm", new Date(val)) - } - }} + aplcnYmFrom={form.watch("aplcnYmFrom")} + setAplcnYmFrom={(val: string) => {form.setValue("aplcnYmFrom", val)}} + aplcnYmTo={form.watch("aplcnYmTo")} + setAplcnYmTo={(val: string) => {form.setValue("aplcnYmTo", val)}} isLoading={isLoadingResult} // 로딩 상태 전달 // Excel 다운로드 핸들러 추가 onExcelDownload={handleExcelDownload} diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx index 9627485aa..b201b3bf9 100644 --- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx +++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx @@ -7,6 +7,7 @@ import { } from "./lib/chart"; import type { CSSProperties } from "react"; +/* const customStyles = { legendContainer: { display: "flex", @@ -17,6 +18,22 @@ const customStyles = { marginTop: "10px", } as CSSProperties, }; +*/ + +const customStyles = { + legendContainer: { + display: "flex", + flexWrap: "wrap", + justifyContent: "center", + alignItems: "center", + gap: "12px", + fontSize: "13px", + fontWeight: "normal", + marginTop: "16px", + marginBottom: "8px", + minHeight: "32px", + } as CSSProperties, +}; type BarChartProps = { chartConfig: ChartConfig | undefined; @@ -60,7 +77,7 @@ const CustomTooltip = ({ }; // 커스텀 범례 (글씨 크기 키우기 + 스타일 추가) -const CustomLegend = (props: any) => { +/*const CustomLegend = (props: any) => { const { payload } = props; return (
@@ -72,6 +89,38 @@ const CustomLegend = (props: any) => { ))}
); +};*/ +const CustomLegend = (props: any) => { + const { payload } = props; + if (!payload) return null; + + return ( +
+ {payload.map((entry: any, index: number) => ( +
+
+ {entry.value} +
+ ))} +
+ ); }; export default function DoubleBarChart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) { @@ -83,7 +132,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha .map(key => item[key]) ).flat()); }; - console.log(chartData); + const barMaxValue = getMaxValue(chartData, 'barVal'); return ( <> diff --git a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx index 39dee3d9e..99395267a 100644 --- a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx +++ b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx @@ -48,6 +48,7 @@ type LegendProps = { }; /** 차트 기본 스타일 */ +/* const customStyles = { legendContainer: { display: "flex", @@ -58,6 +59,23 @@ const customStyles = { marginTop: "10px", } as CSSProperties, }; +*/ + +/** 차트 기본 스타일 (라인차트와 동일하게) */ +const customStyles = { + legendContainer: { + display: "flex", + flexWrap: "wrap", // 여러 줄로 자동 줄바꿈 + justifyContent: "center", + alignItems: "center", + gap: "12px", // 아이템 간 간격 + fontSize: "13px", + fontWeight: "normal", + marginTop: "16px", + marginBottom: "8px", + minHeight: "32px", // 범례 영역 높이 통일 + } as CSSProperties, +}; // 커스텀 툴팁 const CustomTooltip = ({ active, payload, label }: { @@ -82,6 +100,7 @@ const CustomTooltip = ({ active, payload, label }: { }; // 커스텀 범례 +/* const CustomLegend = (props: LegendProps) => { const { payload } = props; if (!payload) return null; @@ -97,12 +116,52 @@ const CustomLegend = (props: LegendProps) => { ); }; +*/ + +const CustomLegend = (props: LegendProps) => { + const { payload } = props; + if (!payload) return null; + + return ( +
+ {payload.map((entry: LegendEntry, index: number) => ( +
+
+ + {entry.payload?.label || entry.value} + +
+ ))} +
+ ); +}; export default function DoughnutChart(props: { chartConfig: any; chartData: ChartDataItem[] | undefined; chartTitle: string }) { + const legendData = props.chartData?.map(item => ({ + value: item.label, + fill: props.chartConfig?.[item.label]?.fill || 'blue', + })) ?? []; return ( @@ -130,9 +189,9 @@ export default function DoughnutChart(props: { nameKey="label" innerRadius={60} /> - + diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index 4014702b6..28b4ceabf 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -18,6 +18,7 @@ type LineChartProps = { } /** 차트 기본 스타일 */ +/* const customStyles = { legendContainer: { display: "flex", @@ -28,6 +29,23 @@ const customStyles = { marginTop: "10px", } as CSSProperties, }; +*/ + +/** 차트 기본 스타일 */ +const customStyles = { + legendContainer: { + display: "flex", + flexWrap: "wrap", + justifyContent: "center", + alignItems: "center", + gap: "12px", + fontSize: "13px", + fontWeight: "normal", + marginTop: "16px", + marginBottom: "8px", + minHeight: "32px", + } as CSSProperties, +}; type LegendEntry = { value: string; @@ -60,7 +78,7 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: }; // 커스텀 범례 (글씨 크기 키우기 + 스타일 추가) -const CustomLegend = (props: LegendProps) => { +/*const CustomLegend = (props: LegendProps) => { const { payload } = props; if (!payload) return null; @@ -74,6 +92,40 @@ const CustomLegend = (props: LegendProps) => { ))} ); +};*/ + +// 커스텀 범례 (도넛차트와 동일하게) +const CustomLegend = (props: LegendProps) => { + const { payload } = props; + if (!payload) return null; + + return ( +
+ {payload.map((entry: LegendEntry, index: number) => ( +
+
+ {entry.value} +
+ ))} +
+ ); }; type DataType = {label: string; val: string}; diff --git a/packages/common/business_components/src/ui/date_month_picker/index.tsx b/packages/common/business_components/src/ui/date_month_picker/index.tsx index d318b561f..1b97d9ce1 100644 --- a/packages/common/business_components/src/ui/date_month_picker/index.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/index.tsx @@ -17,6 +17,7 @@ type MonthPickerProps = { field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열 selectClass?: string; readOnly?: boolean; + startYear?: number; // 시작 연도 props 추가 }; // yyyyMM → Date 객체 @@ -37,6 +38,7 @@ export const DateMonthPicker = ({ field, selectClass, readOnly = false, + startYear, }: MonthPickerProps) => { const [open, setOpen] = useState(false); @@ -75,6 +77,7 @@ export const DateMonthPicker = ({ diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx index acf17fba4..2b64ab439 100644 --- a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx @@ -28,35 +28,41 @@ export type MonthPickerProps = { newMonth: Date | undefined, shouldClosePopover?: boolean ) => void; + startYear?: number; }; -export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) { +export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPickerProps) { const today = startOfToday(); -const currentYear = today.getFullYear(); + const currentYear = today.getFullYear(); const yearFromProp = format(currentMonth, "yyyy"); -const selectedYear = parseInt(yearFromProp); + const selectedYear = parseInt(yearFromProp); // 현재 월의 시작 날짜 const fullYearStart = parse(yearFromProp, "yyyy", new Date()); const yearEnd = selectedYear === currentYear ? today : endOfYear(fullYearStart); - - - const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i); + + + // startYear가 없으면 기존과 동일하게 (현재년도 - 20년부터 현재년도까지) + // startYear가 있으면 해당 년도부터 현재년도까지, 내림차순으로 정렬 + const yearRange = startYear + ? Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i) + : Array.from({ length: 21 }, (_, i) => currentYear - 20 + i); + const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd }); const getMonthButtonClass = (month: Date) => cn( "inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800", isEqual(month, currentMonth) && - "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900", + "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900", !isEqual(month, currentMonth) && - isEqual(month, getStartOfCurrentMonth()) && - "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50" + isEqual(month, getStartOfCurrentMonth()) && + "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50" ); return ( @@ -77,7 +83,7 @@ const selectedYear = parseInt(yearFromProp); onMonthChange(newDate, false); }} > - + @@ -112,7 +118,7 @@ const selectedYear = parseInt(yearFromProp); onClick={() => onMonthChange(month, true)} > diff --git a/packages/common/business_components/src/ui/loadingBar/index.tsx b/packages/common/business_components/src/ui/loadingBar/index.tsx new file mode 100644 index 000000000..37991d1d2 --- /dev/null +++ b/packages/common/business_components/src/ui/loadingBar/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { LoadingWithAnimation } from '@common/components/ui/loading'; + +interface LoadingOverlayProps { + isLoading: boolean; +} + +const LoadingOverlay: React.FC = ({ isLoading }) => { + if (!isLoading) return null; + + return ( +
+
+ +
+
+ ); +}; + +export default LoadingOverlay; \ No newline at end of file