feat:
- 침입수 분석 페이지에서 유량자료 선택값으로 수정 - BaseLayout.tsx의 searchBox props의 selectBox로 변수명 수정, Excel 다운로드 관련 props 추가 - 침입수분석결과 파일 다운로드, 파일 삭제 기능 구현
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { getData, deleteData } from '@/components/lib/api'
|
||||
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'
|
||||
@@ -7,7 +7,6 @@ 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'
|
||||
import { useEffect, useState, type ReactNode } from 'react'
|
||||
import { handleDownload } from '@/components/lib/utils/fileDownload'
|
||||
|
||||
type TitleName = {
|
||||
mainTitle: string
|
||||
@@ -38,7 +37,7 @@ type BaseLayout = {
|
||||
onSubmitEvent: Function
|
||||
graphBtn?: boolean
|
||||
firstDivBtn?: boolean
|
||||
searchBox?: SelectBox
|
||||
selectBox?: SelectBox
|
||||
showDatePicker?: boolean;
|
||||
enoCd?: string | number
|
||||
setEnoCd?: (enoCd: string) => void
|
||||
@@ -52,6 +51,10 @@ type BaseLayout = {
|
||||
onSwageProcessingZoneChange?: (value: string | number) => void;
|
||||
aplcnYm?: Date
|
||||
setAplcnYm?: (aplcnYm: Date | undefined) => void;
|
||||
// Excel 다운로드 관련 props 추가
|
||||
onExcelDownload?: () => void;
|
||||
onDeleteFile?: () => void;
|
||||
selectedRowData?: any;
|
||||
}
|
||||
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
|
||||
/**
|
||||
@@ -69,7 +72,7 @@ const BaseLayout = ({
|
||||
form,
|
||||
onSubmitEvent,
|
||||
graphBtn = false,
|
||||
searchBox,
|
||||
selectBox,
|
||||
showDatePicker = false,
|
||||
enoCd,
|
||||
setEnoCd,
|
||||
@@ -82,7 +85,11 @@ const BaseLayout = ({
|
||||
selectedSwageProcessingZone,
|
||||
onSwageProcessingZoneChange,
|
||||
aplcnYm,
|
||||
setAplcnYm
|
||||
setAplcnYm,
|
||||
// Excel 다운로드 관련 props 추가
|
||||
onExcelDownload,
|
||||
onDeleteFile,
|
||||
selectedRowData
|
||||
}: BaseLayout) => {
|
||||
|
||||
const defaultOption: ApiSelectOption = { val: '0', text: '선택' };
|
||||
@@ -171,28 +178,19 @@ const BaseLayout = ({
|
||||
|
||||
// 파일 다운로드
|
||||
const isChckFile = () => {
|
||||
if (fileNm === '' || fileNm === undefined) {
|
||||
alert('파일을 선택해주세요');
|
||||
return false
|
||||
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
|
||||
if (onExcelDownload) {
|
||||
onExcelDownload();
|
||||
return;
|
||||
}
|
||||
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm)
|
||||
return true
|
||||
}
|
||||
// 파일 삭제
|
||||
const isDeleteFile = async () => {
|
||||
if (dcrpPplMtncBizSn === '' || dcrpPplMtncBizSn === undefined) {
|
||||
alert('삭제할 파일을 선택해주세요');
|
||||
return false
|
||||
}
|
||||
const data = await deleteData(`/attach-service/api/v1/atfl/attachments/${atflId}`)
|
||||
if (data) {
|
||||
const delData = await deleteData(`v1/dcrpPpl/${dcrpPplMtncBizSn}`);
|
||||
if (delData.status == 200) {
|
||||
alert('삭제되었습니다.')
|
||||
searchBox?.onSearchClick?.();
|
||||
} else {
|
||||
alert('삭제에 실패했습니다.')
|
||||
}
|
||||
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
|
||||
if (onDeleteFile) {
|
||||
onDeleteFile();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +199,9 @@ const BaseLayout = ({
|
||||
<ContentLayout title={titleName.mainTitle} className="flex-grow flex flex-col overflow-hidden">
|
||||
<ListDiv>
|
||||
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
|
||||
{searchBox?.title && (
|
||||
{selectBox?.title && (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">{searchBox.title}</h3>
|
||||
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center w-full">
|
||||
@@ -228,7 +226,7 @@ const BaseLayout = ({
|
||||
<SelectBox2 label={"하수처리장"} value={selectedSwageTreatmentPlant} selectArray={treatmentFacilityOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)} />
|
||||
<SelectBox2 label={"하수처리구역"} value={selectedSwageProcessingZone} selectArray={processingZoneOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageProcessingZoneChange?.(val)} />
|
||||
</GovSidoSigunguSearchForm>
|
||||
{searchBox?.searchChk ? <SearchFormRight><Button onClick={searchBox.onSearchClick}>조회</Button></SearchFormRight> : null}
|
||||
{selectBox?.searchChk ? <SearchFormRight><Button onClick={() => selectBox.onSearchClick?.()}>조회</Button></SearchFormRight> : null}
|
||||
</div>
|
||||
</ListDiv>
|
||||
<ListDiv>
|
||||
|
||||
@@ -141,18 +141,27 @@ const IrSetting = () => {
|
||||
console.log("유량자료 정보:", file4);
|
||||
|
||||
// 모든 필수 파일이 선택되었는지 확인 (UploadedFileInfo 객체와 그 안의 file 객체 모두 확인)
|
||||
if (!file1?.file || !file2?.file || !file3?.file || !file4?.file) {
|
||||
// 유량자료는 선택값
|
||||
if (!file1?.file || !file2?.file || !file3?.file) {
|
||||
alert('모든 분석용 파일을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const filePickers = [
|
||||
// filePicked 유틸리티가 File 객체를 직접 받는지, ArrayBuffer 등을 받는지 확인 필요
|
||||
// 여기서는 File 객체를 받고, 내부적으로 ArrayBuffer로 변환하여 parser에 전달한다고 가정
|
||||
{ file: file2, parser: excelToMatchingResultData },
|
||||
{ file: file3, parser: excelToTimePatternData },
|
||||
{ file: file4, parser: excelToFlowRateData }
|
||||
];
|
||||
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; };
|
||||
|
||||
const filePickers: {
|
||||
file: UploadedFileInfo;
|
||||
parser: (excelData: any) => MatchingResult[] | TimePattern[] | FlowRate[];
|
||||
}[] = [
|
||||
{ file: file2, parser: excelToMatchingResultData },
|
||||
{ file: file3, parser: excelToTimePatternData }
|
||||
];
|
||||
|
||||
if (file4?.file) {
|
||||
filePickers.push({ file: file4, parser: excelToFlowRateData });
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedResults = await Promise.all(
|
||||
@@ -186,7 +195,9 @@ const IrSetting = () => {
|
||||
// 각 파싱 결과 접근
|
||||
const matchingResultData = parsedResults.find(r => r && r.fileName === file2.file?.name)?.parsedData;
|
||||
const timePatternData = parsedResults.find(r => r && r.fileName === file3.file?.name)?.parsedData;
|
||||
const flowRateData = parsedResults.find(r => r && r.fileName === file4.file?.name)?.parsedData;
|
||||
const flowRateData = file4?.file
|
||||
? parsedResults.find(r => r && r.fileName === file4.file?.name)?.parsedData
|
||||
: null;
|
||||
|
||||
// 새로운 JSON 데이터 생성
|
||||
const combinedAnalysisData = {
|
||||
@@ -203,10 +214,10 @@ const IrSetting = () => {
|
||||
atflInpNm: file1?.file?.name, // 오수관망도 파일명
|
||||
atflHrPttnNm: file3?.file?.name, // 오수량 시간패턴 파일명
|
||||
atflMpngNm: file2?.file?.name, // 수용가-맨홀 매칭결과 파일명
|
||||
atflOilqNm: file4?.file?.name, // 유량자료 파일명
|
||||
atflOilqNm: file4?.file?.name || null, // 유량자료 파일명 (없을 수 있음)
|
||||
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
|
||||
tbSnHrPttn: timePatternData, // 오수량 시간패턴
|
||||
tbSnOilqData: flowRateData, // 유량자료
|
||||
tbSnOilqData: flowRateData, // 유량자료 (없을 수 있음)
|
||||
};
|
||||
|
||||
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
|
||||
@@ -228,7 +239,7 @@ const IrSetting = () => {
|
||||
form={form}
|
||||
onSubmitEvent={() => { }}
|
||||
graphBtn={false}
|
||||
searchBox={{ title: '분석 대상지역 선택' }}
|
||||
selectBox={{ title: '분석 대상지역 선택' }}
|
||||
enoCd={select1}
|
||||
setEnoCd={setSelect1}
|
||||
ctpvCd={select2}
|
||||
|
||||
@@ -313,7 +313,7 @@ export default function RTKForm() {
|
||||
return (
|
||||
// <BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
|
||||
<BaseLayout
|
||||
searchBox={{ title: '분석 대상지역 선택' }}
|
||||
selectBox={{ title: '분석 대상지역 선택' }}
|
||||
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
|
||||
form={form} // react-hook-form 인스턴스 전달
|
||||
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
|
||||
|
||||
+117
-27
@@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
import { getData } from '@/components/lib/api'
|
||||
import { UiTable } from '@common/business_components/ui'
|
||||
import { getData, deleteData } from '@/components/lib/api'
|
||||
import { UiTable, downloadExcel } from '@common/business_components/ui'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import BaseLayout from '../BaseLayout'
|
||||
|
||||
|
||||
interface ApiSelectOption {
|
||||
text: string;
|
||||
val: string;
|
||||
@@ -27,7 +26,6 @@ const formSchema = z.object({
|
||||
|
||||
type ResultFormValues = z.infer<typeof formSchema>
|
||||
|
||||
// TODO: 분석구분에 따라서 테이블 컬럼 변경필요
|
||||
const tableColumn = [
|
||||
// { id: 'no', title: '번호' },
|
||||
{ id: 'inftwtAnlsSn', title: '분석번호' },
|
||||
@@ -36,14 +34,8 @@ const tableColumn = [
|
||||
{ id: 'aplcnYm', title: '적용연월' },
|
||||
{ id: 'drtwSwtcRt', title: '오수 전환율' },
|
||||
{ id: 'anlsInfo', title: '침입수 분석정보' },
|
||||
{
|
||||
id: 'regDt',
|
||||
title: '분석일자',
|
||||
},
|
||||
{
|
||||
id: 'rgtrNm',
|
||||
title: '분석자',
|
||||
}
|
||||
{ id: 'regDt', title: '분석일자' },
|
||||
{ id: 'rgtrNm', title: '분석자' }
|
||||
]
|
||||
const tableColumn2 = [
|
||||
{ id: 'no', title: '번호' },
|
||||
@@ -75,21 +67,36 @@ const Result = () => {
|
||||
|
||||
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
|
||||
const [totalData, setTotalData] = useState(0) //총 데이터 수
|
||||
const [rdiiResultData, setRdiiResultData] = useState<any[]>([]); // RDII 결과 저장 상태 (필요시)
|
||||
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
|
||||
const [selectedRowData, setSelectedRowData] = useState<any>(null);
|
||||
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
const handleSearchButtonClick = async () => {
|
||||
|
||||
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
|
||||
setIsLoadingResult(true);
|
||||
const formValues = form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
console.log("formValues", formValues);
|
||||
|
||||
const inftwtAnlsSearchDto = {
|
||||
"aplcnYm": formValues.aplcnYm.toISOString().substring(0, 7).replace("-", ""),
|
||||
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
|
||||
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
|
||||
"sggCd": formValues.sggCd.val === '0' ? null : formValues.sggCd.val,
|
||||
// "swageTmplntCd": formValues.swageTmplntCd === '0' ? null : formValues.swageTmplntCd,
|
||||
"swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
|
||||
};
|
||||
let inftwtAnlsSearchDto;
|
||||
|
||||
if (customFormValues) {
|
||||
inftwtAnlsSearchDto = {
|
||||
"aplcnYm": formValues.aplcnYm,
|
||||
"enoCd": formValues.enoCd,
|
||||
"ctpvCd": formValues.ctpvCd,
|
||||
"sggCd": formValues.sggCd,
|
||||
"swtrmtZoneCd": formValues.swtrmtZoneCd,
|
||||
};
|
||||
} else {
|
||||
inftwtAnlsSearchDto = {
|
||||
"aplcnYm": formValues.aplcnYm ? formValues.aplcnYm.toISOString().substring(0, 7).replace("-", "") : 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,
|
||||
};
|
||||
}
|
||||
console.log("Search DTO:", inftwtAnlsSearchDto);
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
page: '0',
|
||||
@@ -103,7 +110,7 @@ const Result = () => {
|
||||
});
|
||||
const queryString = queryParams.toString();
|
||||
|
||||
console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`); // 디버깅용 로그
|
||||
console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
|
||||
try {
|
||||
const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
|
||||
setTableData(data.content || []);
|
||||
@@ -120,9 +127,89 @@ const Result = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 테이블 셀 클릭 핸들러 추가
|
||||
const handleCellClick = (index: number | undefined, headId: string) => {
|
||||
if (index !== undefined && tableData[index]) {
|
||||
setSelectedRowData(tableData[index]);
|
||||
setSelectedRowIndex(index);
|
||||
console.log("Selected row data:", tableData[index]);
|
||||
}
|
||||
};
|
||||
|
||||
// Excel 다운로드 핸들러 추가
|
||||
const handleExcelDownload = async () => {
|
||||
console.log("Selected row data for download:", selectedRowData);
|
||||
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
|
||||
alert('다운로드할 데이터를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await getData(`v1/inftwtAnlsRslt/ppl/${selectedRowData.inftwtAnlsSn}`);
|
||||
|
||||
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
|
||||
const excelData = data.content; // 배열로 감싸서 전달
|
||||
|
||||
// 파일명 생성 (분석번호 기반)
|
||||
const formValues = form.getValues();
|
||||
const fileName = `침입수분석결과_${selectedRowData.inftwtAnlsSn}_${selectedRowData.aplcnYm}`;
|
||||
|
||||
downloadExcel({
|
||||
data: excelData as JSON[],
|
||||
fileName: fileName,
|
||||
sheetNameArray: ["침입수 분석결과"],
|
||||
headerRange: "A1:D1" // 테이블 컬럼 수에 맞춰 조정
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Excel 다운로드 중 오류 발생:", error);
|
||||
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
|
||||
}
|
||||
};
|
||||
|
||||
// 파일 삭제 핸들러 추가
|
||||
const handleFileDelete = async () => {
|
||||
console.log("Selected row data for deletion:", selectedRowData);
|
||||
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
|
||||
alert('삭제할 파일을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
// 삭제 확인 대화상자
|
||||
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
|
||||
if (!confirmDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 삭제 API 호출
|
||||
const response1 = await deleteData(`v1/inftwtAnlsRslt/${selectedRowData.inftwtAnlsSn}`);
|
||||
const response2 = await deleteData(`v1/inftwtAnls/${selectedRowData.inftwtAnlsSn}`);
|
||||
console.log("File delete response1:", response1);
|
||||
console.log("File delete response2:", response2);
|
||||
|
||||
// 삭제 후 테이블 새로고침을 위해 재검색
|
||||
const customFormValues: Partial<ResultFormValues> = {
|
||||
aplcnYm: selectedRowData.aplcnYm || '0',
|
||||
enoCd: selectedRowData.enoCd || '0',
|
||||
ctpvCd: selectedRowData.ctpvCd || '0',
|
||||
sggCd: selectedRowData.sggCd || '0',
|
||||
swtrmtZoneCd: selectedRowData.swtrmtZoneCd || '0',
|
||||
};
|
||||
await handleSearchButtonClick(customFormValues);
|
||||
|
||||
// 선택된 행 초기화
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
|
||||
alert('삭제가 완료되었습니다.');
|
||||
} catch (error) {
|
||||
console.error("분석결과 파일 삭제 중 오류 발생:", error);
|
||||
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
searchBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
|
||||
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
|
||||
titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력', secondCardTitle: 'RDII 산정결과 현황' }}
|
||||
form={form} // form 인스턴스 전달
|
||||
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
|
||||
@@ -146,7 +233,10 @@ const Result = () => {
|
||||
form.setValue("aplcnYm", new Date(val))
|
||||
}
|
||||
}}
|
||||
// DatePicker는 BaseLayout 내부에서 form.watch("aplcnYm")과 form.setValue("aplcnYm", ...)을 사용하도록 수정 필요
|
||||
// Excel 다운로드 핸들러 추가
|
||||
onExcelDownload={handleExcelDownload}
|
||||
onDeleteFile={handleFileDelete}
|
||||
selectedRowData={selectedRowData}
|
||||
>
|
||||
{[<div key='table1' className="h-[55vh] flex flex-col p-4 border rounded-lg bg-white shadow-md">
|
||||
<div className="mb-[1.2rem] text-xl font-medium">○ 침입수 산정결과 현황</div>
|
||||
@@ -158,7 +248,7 @@ const Result = () => {
|
||||
total={totalData}
|
||||
pageSize={10}
|
||||
children={undefined}
|
||||
cellClick={(e) => { }}
|
||||
cellClick={handleCellClick}
|
||||
onPageChange={(e) => { }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user