rafactor: tab size 4 -> 2로 수정

This commit is contained in:
swcho
2025-06-25 14:24:30 +09:00
parent 1cfae09b33
commit f679277192
29 changed files with 2976 additions and 2975 deletions
+156 -156
View File
@@ -438,43 +438,43 @@ const SearchModal: React.FC<SearchModalProps> = ({
// 검색 API 호출 함수
const fetchFilteredData = async () => {
setIsLoading(true); // 로딩 시작
try {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
page: `${curPage - 1}`,
size: '10',
sort: 'string',
});
setIsLoading(true); // 로딩 시작
try {
const queryParams = new URLSearchParams({
lyrTypeNm: basicType,
page: `${curPage - 1}`,
size: '10',
sort: 'string',
});
let cqlFilters: string[] = [];
if (activeTab === 'basic') {
if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = zoneCqlFilters();
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters();
else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters();
} else if (activeTab === 'operation') {
cqlFilters = zoneCqlFilters();
} else {
cqlFilters = reportCqlFilters();
}
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`);
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
console.log('필터링된 API 결과(개수):', res.data.count);
console.log('필터링된 API 결과(데이터):', res.data.data);
setTableData(res.data.data || []);
setTableCount(res.data.count || []);
console.log('tableCount 상태 업데이트 후:', res.data.count); // tableCount 로그 추가
setShowTable(true);
} catch (err) {
console.error('API ERROR:', err);
setTableData([]);
} finally {
setIsLoading(false); // 로딩 종료
let cqlFilters: string[] = [];
if (activeTab === 'basic') {
if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = zoneCqlFilters();
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters();
else if (basicType === 'tb_ms_swage_ppl') cqlFilters = pplCqlFilters();
} else if (activeTab === 'operation') {
cqlFilters = zoneCqlFilters();
} else {
cqlFilters = reportCqlFilters();
}
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`);
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
console.log('필터링된 API 결과(개수):', res.data.count);
console.log('필터링된 API 결과(데이터):', res.data.data);
setTableData(res.data.data || []);
setTableCount(res.data.count || []);
console.log('tableCount 상태 업데이트 후:', res.data.count); // tableCount 로그 추가
setShowTable(true);
} catch (err) {
console.error('API ERROR:', err);
setTableData([]);
} finally {
setIsLoading(false); // 로딩 종료
}
};
const renderBody = () => (
@@ -814,83 +814,83 @@ const SearchModal: React.FC<SearchModalProps> = ({
{/* 분기처리: 하수펌프장 */}
{(basicType === "tb_ms_swage_psmtn_1" ||
basicType === "tb_ms_swage_psmtn_2") && (
<div className="mb-3">
{/* 설치년도 */}
<div className="flex items-center mb-2">
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={range => setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
<Label className="w-auto text-xl text-gray-600 mr-2">경과년도</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={yearFilter.경과년도}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
<div className="mb-3">
{/* 설치년도 */}
<div className="flex items-center mb-2">
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={range => setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
<Label className="w-auto text-xl text-gray-600 mr-2">경과년도</Label>
<Input
className='!text-xl w-[100px] placeholder:text-center mr-8'
placeholder="입력"
value={yearFilter.경과년도}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, '');
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
</div>
<div className="flex items-center mb-2">
{/* 일일처리용량 - 오수맨홀 */}
<Label className="mr-2 w-auto text-xl text-gray-600">
일일처리용량
</Label>
<Input
className="w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={시작1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set시작1(val);
}}
/>
<span className="mx-2 text-xl font-semibold">~</span>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={종료1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set종료1(val);
}}
/>
{/* 우수양수능력 - 우수맨홀 */}
<Label className="mr-2 w-auto text-xl text-gray-600">
우수양수능력
</Label>
<Input
className="w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={시작2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set시작2(val);
}}
/>
<span className="mx-2 text-xl font-semibold">~</span>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={종료2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set종료2(val);
}}
/>
</div>
</div>
<div className="flex items-center mb-2">
{/* 일일처리용량 - 오수맨홀 */}
<Label className="mr-2 w-auto text-xl text-gray-600">
일일처리용량
</Label>
<Input
className="w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={시작1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set시작1(val);
}}
/>
<span className="mx-2 text-xl font-semibold">~</span>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={종료1}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set종료1(val);
}}
/>
{/* 우수양수능력 - 우수맨홀 */}
<Label className="mr-2 w-auto text-xl text-gray-600">
우수양수능력
</Label>
<Input
className="w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={시작2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set시작2(val);
}}
/>
<span className="mx-2 text-xl font-semibold">~</span>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={종료2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
set종료2(val);
}}
/>
</div>
</div>
)}
)}
{/* 분기처리: 우수토실? 토구? */}
{basicType === "tb_ms_otfl" && (
<div className="mb-3">
@@ -981,20 +981,20 @@ const SearchModal: React.FC<SearchModalProps> = ({
/>
{/* 설치년도 */}
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={(range) =>
setYearFilter({
...yearFilter,
시작: range.start,
종료: range.end,
})
}
startLabel="설치년도"
endLabel=""
/>
<div className="flex items-center">
<YearRangeSelectBox
start={yearFilter.시작}
end={yearFilter.종료}
onChange={(range) =>
setYearFilter({
...yearFilter,
시작: range.start,
종료: range.end,
})
}
startLabel="설치년도"
endLabel=""
/>
<div className="flex items-center">
{/* 경과년도 */}
<Label className="mr-2 w-auto text-xl text-gray-600">
경과년도
@@ -1009,12 +1009,12 @@ const SearchModal: React.FC<SearchModalProps> = ({
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
</div>
</div>
{/* 조회 버튼 중앙 정렬 */}
<div className="absolute bottom-4 right-4 mr-2">
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
</div>
</div>
{/* 조회 버튼 중앙 정렬 */}
<div className="absolute bottom-4 right-4 mr-2">
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
</div>
</div>
)}
{/* 하수도정비기본계획보고서 탭 */}
@@ -1026,9 +1026,9 @@ const SearchModal: React.FC<SearchModalProps> = ({
sido={시도}
sigungu={시군구}
onSidoChange={(sidoId) => {
set시도(sidoId);
set시군구(0);
}}
set시도(sidoId);
set시군구(0);
}}
onSigunguChange={set시군구}
/>
</div>
@@ -1071,28 +1071,28 @@ const SearchModal: React.FC<SearchModalProps> = ({
{/* 📋 결과 리스트 */}
<div className="h-[60vh] w-full space-y-2 overflow-hidden rounded-lg border">
{/* 테이블 영역 (가로/세로 스크롤 가능) */}
<div className="flex-1 px-2 pb-2">
<LoadingOverlay isLoading={isLoading}/>
<UiTable2
headName={
(() => {
const resultCount = tableData.length;
const label =
activeTab === "basic"
? BASIC_TYPES.find((type) => type.val === basicType)?.text
: activeTab === "operation"
? WORK_TYPES.find((type) => type.val === workType)?.text
: activeTab === "report"
? "하수도정비기본계획보고서"
: TABS.find((tab) => tab.val === activeTab)?.text;
return (
<>
{label} 조회 결과 ({tableCount})건
</>
);
})()
}
{/* 테이블 영역 (가로/세로 스크롤 가능) */}
<div className="flex-1 px-2 pb-2">
<LoadingOverlay isLoading={isLoading} />
<UiTable2
headName={
(() => {
const resultCount = tableData.length;
const label =
activeTab === "basic"
? BASIC_TYPES.find((type) => type.val === basicType)?.text
: activeTab === "operation"
? WORK_TYPES.find((type) => type.val === workType)?.text
: activeTab === "report"
? "하수도정비기본계획보고서"
: TABS.find((tab) => tab.val === activeTab)?.text;
return (
<>
{label} 조회 결과 ({tableCount})건
</>
);
})()
}
tableData={tableData}
headlist={dynamicHeadlist}
pageSize={tableData.length < 10 ? tableData.length : 10}
@@ -1103,8 +1103,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
resetPageNo={false}
cur={curPage}
/>
</div>
</div>
</div>
</>
);
@@ -2,49 +2,49 @@ import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@
import React from "react";
interface InputSectionProps {
title: string;
data: { year: string; type: string }[];
sectionKey: string;
onInputChange: (section: string, index: number, field: string, value: string) => void;
title: string;
data: { year: string; type: string }[];
sectionKey: string;
onInputChange: (section: string, index: number, field: string, value: string) => void;
}
const InputSection: React.FC<InputSectionProps> = ({ title, data, sectionKey, onInputChange }) => {
return (
<div>
{/* 섹션 제목 */}
<div className="card-header">
<h3 className="text-2xl mx-3 font-medium text-label">{title}</h3>
</div>
{/* 테이블 */}
<div className="card !m-[1.2rem] !p-0r">
<Table variant="secondary" className="w-full">
<TableHeader>
<TableRow>
<TableHead className="border border-gray-300 px-2 py-2 text-sm">년도</TableHead>
<TableHead className="border border-gray-300 px-2 py-2 text-sm">구분</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, index) => (
<TableRow key={index}>
<TableCell className="border border-gray-300 px-2 py-2 text-sm">
<input
type="text"
value={row.year}
onChange={(e) => onInputChange(sectionKey, index, "year", e.target.value)}
className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal"
/>
</TableCell>
<TableCell className="border border-gray-300 px-2 py-2 text-sm text-center leading-normal">
{row.type}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
return (
<div>
{/* 섹션 제목 */}
<div className="card-header">
<h3 className="text-2xl mx-3 font-medium text-label">{title}</h3>
</div>
{/* 테이블 */}
<div className="card !m-[1.2rem] !p-0r">
<Table variant="secondary" className="w-full">
<TableHeader>
<TableRow>
<TableHead className="border border-gray-300 px-2 py-2 text-sm">년도</TableHead>
<TableHead className="border border-gray-300 px-2 py-2 text-sm">구분</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((row, index) => (
<TableRow key={index}>
<TableCell className="border border-gray-300 px-2 py-2 text-sm">
<input
type="text"
value={row.year}
onChange={(e) => onInputChange(sectionKey, index, "year", e.target.value)}
className="w-full text-xl border-radius-0 px-3 py-2 text-base text-center leading-normal"
/>
</TableCell>
<TableCell className="border border-gray-300 px-2 py-2 text-sm text-center leading-normal">
{row.type}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
};
export default InputSection;
@@ -6,57 +6,57 @@ import StormWaterPump from "../popupComponent/stormWaterPump";
import SwtrmtFclt from "../popupComponent/SwtrmtFclt";
type LayerComponentProps = {
featureData: any;
chkMainSsc?: boolean;
additionalData?: any;
featureData: any;
chkMainSsc?: boolean;
additionalData?: any;
};
type LayerContentMap = {
[key: string]: {
component?: React.FC<LayerComponentProps>;
props?: any;
};
[key: string]: {
component?: React.FC<LayerComponentProps>;
props?: any;
};
};
// 레이어 이름과 컴포넌트를 매핑
// 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업
const layerContentMap: LayerContentMap = {
// TODO: gis데이터 추가되면 추가 작업 필요
"하수처리시설": { component: SwtrmtFclt, },
"하수처리구역": { },
"우수토실":{component: Ssc,},
"중점우수토실":{component: Ssc, }, // 중점우수토실 여부 설정
"하수관로":{component: MsSwagePpl,},
"하수맨홀":{component: smh,},
"빗물펌프":{component:StormWaterPump},
"오수펌프":{component:sewagePump},
// TODO: gis데이터 추가되면 추가 작업 필요
"하수처리시설": { component: SwtrmtFclt, },
"하수처리구역": {},
"우수토실": { component: Ssc, },
"중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정
"하수관로": { component: MsSwagePpl, },
"하수맨홀": { component: smh, },
"빗물펌프": { component: StormWaterPump },
"오수펌프": { component: sewagePump },
};
// 동적 컴포넌트 반환 함수
export const getLayerContentComponent = (layerNm: string): React.FC<LayerComponentProps> | null => {
const component = layerContentMap[layerNm];
if (!component || !component.component) {
// component가 없으면 null 반환
return () => null;
}
return component.component
const component = layerContentMap[layerNm];
if (!component || !component.component) {
// component가 없으면 null 반환
return () => null;
}
return component.component
};
// 사용자 입력 필드 (보수 및 갱신, 청소 및 준설, 점검 및 진단)
export const handleInputChange = (section: string,
index: number,
field: string,
value: string,
dataMap: {
[key: string]: {
data: { year: string; type: string }[];
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string }[]>>;
};
}
index: number,
field: string,
value: string,
dataMap: {
[key: string]: {
data: { year: string; type: string }[];
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string }[]>>;
};
}
) => {
const target = dataMap[section];
if (!target) return;
const target = dataMap[section];
if (!target) return;
const updated = [...target.data];
if (updated[index]) {
updated[index][field as keyof typeof updated[number]] = value;
}
target.setData(updated);
const updated = [...target.data];
if (updated[index]) {
updated[index][field as keyof typeof updated[number]] = value;
}
target.setData(updated);
};
@@ -1,128 +1,128 @@
// 하수처리시설
export const SwtrmtFcltCol = [
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
{ id: 'fcltCpct', title: '시설용량' },
{ id: 'rgnExpln', title: '지역설명' },
{ id: 'cmcnYmd', title: '준공일자' },
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
{ id: 'clsgYmd', title: '폐쇄일자' },
{ id: 'prcsMsrcCd', title: '처리공법코드' },
// { id: 'laeCd', title: '지자체코드' },
{ id: 'laeNm', title: '지자체명' },
{ id: 'ctpvNm', title: '시도명' },
{ id: 'sggNm', title: '시군구명' },
// { id: 'sareaCd', title: '권역코드' },
{ id: 'sareaNm', title: '권역명' },
{ id: 'lrgeSareaNm', title: '대권역명' },
{ id: 'rvrNm', title: '하천명' },
{ id: 'addr', title: '주소' },
{ id: 'daddr', title: '상세주소' },
{ id: 'rdnmAddr', title: '도로명주소' },
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
{ id: 'dschgWtstNm', title: '방류수계명' },
{ id: 'pllnArea', title: '부지면적' },
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
{ id: 'xcrd', title: 'X좌표' },
{ id: 'ycrd', title: 'Y좌표' },
// { id: 'oprtngSttsCd', title: '가동상태코드' },
{ id: 'rprsMsrcCn', title: '대표공법내용' },
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
{ id: 'sttyCrtrSs', title: '법정기준SS' },
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
{ id: 'sttyCrtrTph', title: '법정기준총인' },
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
{ id: 'bsnNm', title: '유역명' },
// { id: 'obsrvnSptCd', title: '관측지점코드' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
{ id: 'fcltCpct', title: '시설용량' },
{ id: 'rgnExpln', title: '지역설명' },
{ id: 'cmcnYmd', title: '준공일자' },
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
{ id: 'clsgYmd', title: '폐쇄일자' },
{ id: 'prcsMsrcCd', title: '처리공법코드' },
// { id: 'laeCd', title: '지자체코드' },
{ id: 'laeNm', title: '지자체명' },
{ id: 'ctpvNm', title: '시도명' },
{ id: 'sggNm', title: '시군구명' },
// { id: 'sareaCd', title: '권역코드' },
{ id: 'sareaNm', title: '권역명' },
{ id: 'lrgeSareaNm', title: '대권역명' },
{ id: 'rvrNm', title: '하천명' },
{ id: 'addr', title: '주소' },
{ id: 'daddr', title: '상세주소' },
{ id: 'rdnmAddr', title: '도로명주소' },
{ id: 'dtlRdnmAddr', title: '상세도로명주소' },
{ id: 'dschgWtstNm', title: '방류수계명' },
{ id: 'pllnArea', title: '부지면적' },
{ id: 'mngRprtvBzentyNm', title: '관리대행업제명' },
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수' },
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계' },
{ id: 'xcrd', title: 'X좌표' },
{ id: 'ycrd', title: 'Y좌표' },
// { id: 'oprtngSttsCd', title: '가동상태코드' },
{ id: 'rprsMsrcCn', title: '대표공법내용' },
{ id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량' },
{ id: 'sttyCrtrToc', title: '법정기준TOC' },
{ id: 'sttyCrtrSs', title: '법정기준SS' },
{ id: 'sttyCrtrTn', title: '법정기준총질소' },
{ id: 'sttyCrtrTph', title: '법정기준총인' },
{ id: 'sttyCrtrClbclCnt', title: '법정기준대장균수' },
{ id: 'bsnNm', title: '유역명' },
// { id: 'obsrvnSptCd', title: '관측지점코드' },
];
// 하수처리구역
export const SwtrmtPrcsZoneCol = [
{ id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
// { id: 'swtrmtFcltCd', title: '하수처리시설코드' },
// { id: 'ctpvCd', title: '시도코드' },
{ id: 'ctpvNm', title: '시도명' },
// { id: 'sggCd', title: '시군구코드' },
{ id: 'sggNm', title: '시군구명' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
{ id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
// { id: 'swtrmtFcltCd', title: '하수처리시설코드' },
// { id: 'ctpvCd', title: '시도코드' },
{ id: 'ctpvNm', title: '시도명' },
// { id: 'sggCd', title: '시군구코드' },
{ id: 'sggNm', title: '시군구명' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
];
// 하수맨홀
export const MsSwageMhlCol = [
{ id: 'fid', title: 'FID' },
{ id: 'geom', title: 'geometry' },
{ id: 'ftreSmblCd', title: '지형지물부호코드' },
{ id: 'mngNo', title: '관리번호' },
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
{ id: 'mapshtNo', title: '도엽번호' },
// { id: 'mngInstCd', title: '관리기관코드' },
{ id: 'instlYmd', title: '설치일자' },
{ id: 'lastDrdgnYmd', title: '최종준설일자' },
{ id: 'usgCd', title: '용도코드' },
// { id: 'fcltyShpCd', title: '시설물형태코드' },
{ id: 'mhlKndCd', title: '맨홀종류코드' },
{ id: 'swageMhDiam', title: '하수맨홀구경' },
{ id: 'wdthLen', title: '가로길이' },
{ id: 'vrtcLen', title: '세로길이' },
// { id: 'mqltCd', title: '재질코드' },
{ id: 'altdVl', title: '표고값' },
{ id: 'dpthlVl', title: '심도값' },
{ id: 'instMngNo', title:'기관관리번호'}
{ id: 'fid', title: 'FID' },
{ id: 'geom', title: 'geometry' },
{ id: 'ftreSmblCd', title: '지형지물부호코드' },
{ id: 'mngNo', title: '관리번호' },
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
{ id: 'mapshtNo', title: '도엽번호' },
// { id: 'mngInstCd', title: '관리기관코드' },
{ id: 'instlYmd', title: '설치일자' },
{ id: 'lastDrdgnYmd', title: '최종준설일자' },
{ id: 'usgCd', title: '용도코드' },
// { id: 'fcltyShpCd', title: '시설물형태코드' },
{ id: 'mhlKndCd', title: '맨홀종류코드' },
{ id: 'swageMhDiam', title: '하수맨홀구경' },
{ id: 'wdthLen', title: '가로길이' },
{ id: 'vrtcLen', title: '세로길이' },
// { id: 'mqltCd', title: '재질코드' },
{ id: 'altdVl', title: '표고값' },
{ id: 'dpthlVl', title: '심도값' },
{ id: 'instMngNo', title: '기관관리번호' }
];
// 중점우수토실 측정지점
export const untrtSwgRainCol1 = [
{ id: 'manage_no', title: '관리번호' },
{ id: 'swr_ppl_eqp_nm', title: '처리구역' },
{ id: 'swr_ppl_trt_nm', title: '처리분구' },
{ id: 'addr', title: '인근주소' },
{ id: 'chmbr_xcnts', title: '토실 위도' },
{ id: 'chmbr_ydnts', title: '토실 경도' }
{ id: 'manage_no', title: '관리번호' },
{ id: 'swr_ppl_eqp_nm', title: '처리구역' },
{ id: 'swr_ppl_trt_nm', title: '처리분구' },
{ id: 'addr', title: '인근주소' },
{ id: 'chmbr_xcnts', title: '토실 위도' },
{ id: 'chmbr_ydnts', title: '토실 경도' }
];
// 중점우수토실 측정지점 시설현황
export const untrtSwgRainCol2 = [
{ id: 'flwmtr_mesure_tp_etc', title: '구분' },
{ id: 'discharge_pipe_spec', title: '방류관로 규격' },
{ id: 'swr_ppl_eqp_ar', title: '배수구역 면적(m²)' },
{ id: 'swr_trt_popl', title: '하수처리 인구(인)' },
{ id: 'screen_instl_at', title: '스크린설치 여부' },
{ id: 'weir_hg', title: '위어 높이(m)' },
{ id: 'weir_lt', title: '위어 길이(m)' }
{ id: 'flwmtr_mesure_tp_etc', title: '구분' },
{ id: 'discharge_pipe_spec', title: '방류관로 규격' },
{ id: 'swr_ppl_eqp_ar', title: '배수구역 면적(m²)' },
{ id: 'swr_trt_popl', title: '하수처리 인구(인)' },
{ id: 'screen_instl_at', title: '스크린설치 여부' },
{ id: 'weir_hg', title: '위어 높이(m)' },
{ id: 'weir_lt', title: '위어 길이(m)' }
];
// 중점우수토실 강우정보 (id값 틀릴 수 있음)
export const untrtSwgRainCol3 = [
{ id: 'rain_begin_dt', title: '강우시작일시' },
{ id: 'rain_end_dt', title: '강우종료일시' },
{ id: 'precdng_dry', title: '선행건기(h)' },
{ id: 'rain_info', title: '강우정보' },
{ id: 'rain_fall', title: '누적강우량(mm)' },
{ id: 'cntnc_time', title: '지속시간(h)' },
{ id: 'rain_in', title: '강우강도(mm/h)' }
{ id: 'rain_begin_dt', title: '강우시작일시' },
{ id: 'rain_end_dt', title: '강우종료일시' },
{ id: 'precdng_dry', title: '선행건기(h)' },
{ id: 'rain_info', title: '강우정보' },
{ id: 'rain_fall', title: '누적강우량(mm)' },
{ id: 'cntnc_time', title: '지속시간(h)' },
{ id: 'rain_in', title: '강우강도(mm/h)' }
];
// 중점우수토실 측정정보 (id값 틀릴 수 있음)
export const untrtSwgRainCol4 = [
{ id: 'mesure_begin_dt', title: '측정시작일시' },
{ id: 'mesure_end_dt', title: '측정종료일시' },
{ id: 'mesure_intrvl', title: '측정간격(분)' },
{ id: 'mesure_mhrls_instl_lc', title: '측정기기 설치위치' },
{ id: 'flwmtr_combined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
{ id: 'qltwtr_begin_dt', title: '채수시작일시' },
{ id: 'qltwtr_end_dt', title: '채수종료일시' },
{ id: 'qltwtr_co', title: '채수횟수' }
{ id: 'mesure_begin_dt', title: '측정시작일시' },
{ id: 'mesure_end_dt', title: '측정종료일시' },
{ id: 'mesure_intrvl', title: '측정간격(분)' },
{ id: 'mesure_mhrls_instl_lc', title: '측정기기 설치위치' },
{ id: 'flwmtr_combined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
{ id: 'qltwtr_begin_dt', title: '채수시작일시' },
{ id: 'qltwtr_end_dt', title: '채수종료일시' },
{ id: 'qltwtr_co', title: '채수횟수' }
];
// 중점우수토실 수량/수질측정 정보
export const untrtSwgRainCol5 = [
{ id: 'odr', title: '회차' },
{ id: 'mesureDt', title: '측정일자' },
{ id: 'mesureQy', title: '수량(m)' },
{ id: 'mesureBod', title: 'BOD(mg/L)' },
{ id: 'mesureToc', title: 'TOC(mg/L)' },
{ id: 'mesureLoad', title: '오염부하량(kg)' }
{ id: 'odr', title: '회차' },
{ id: 'mesureDt', title: '측정일자' },
{ id: 'mesureQy', title: '수량(m)' },
{ id: 'mesureBod', title: 'BOD(mg/L)' },
{ id: 'mesureToc', title: 'TOC(mg/L)' },
{ id: 'mesureLoad', title: '오염부하량(kg)' }
];
@@ -7,81 +7,81 @@ import InfilChart from './chart/infilChart';
// 하수관로
// TODO: RDII 버튼인가 그거 팝업 두개 구현 필요
const inputColumn = [
{ id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "basu", title: "배수구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] },
{ id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] },
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
{ id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "basu", title: "배수구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] },
{ id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] },
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
];
const SwtrmtFclt = ({ featureData }: { featureData: any }) => {
console.log("featureData", featureData);
const [isModalOpen, setIsModalOpen] = React.useState(false);
const [btnTxt, setBtnTxt] = useState<string>("");
const openModal = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent)
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
const [repairData, setRepairData] = useState([
{ year: "2025", type: "개대체" },
{ year: "2025", type: "보수" },
]);
const [cleaningData, setCleaningData] = useState([
{ year: "2025", type: "청소" },
{ year: "2025", type: "준설" },
]);
const [inspectionData, setInspectionData] = useState([
{ year: "2025", type: "관로기술진단" },
{ year: "2025", type: "관로기술진단" },
]);
const dataMap = {
repair: { data: repairData, setData: setRepairData },
cleaning: { data: cleaningData, setData: setCleaningData },
inspection: { data: inspectionData, setData: setInspectionData },
};
const keysToExtract = [
"swtrmt_zone_cd",
"mltv_dtdv_nm",
"fid",
"lyrTypeNm",
"reg_dt",
"mdfcn_dt",
"rgtr",
"mdfr",
];
// const filteredData = Object.entries(featureData)
// .filter(([key]) => keysToExtract.includes(key)) // 필요한 키만 필터링
// .map(([key, value]) => ({ key, value: value ?? "N/A" })); // 테이블 데이터 형식으로 변환
console.log("featureData", featureData);
const [isModalOpen, setIsModalOpen] = React.useState(false);
const [btnTxt, setBtnTxt] = useState<string>("");
const openModal = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent)
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
};
const [repairData, setRepairData] = useState([
{ year: "2025", type: "개대체" },
{ year: "2025", type: "보수" },
]);
const [cleaningData, setCleaningData] = useState([
{ year: "2025", type: "청소" },
{ year: "2025", type: "준설" },
]);
const [inspectionData, setInspectionData] = useState([
{ year: "2025", type: "관로기술진단" },
{ year: "2025", type: "관로기술진단" },
]);
const dataMap = {
repair: { data: repairData, setData: setRepairData },
cleaning: { data: cleaningData, setData: setCleaningData },
inspection: { data: inspectionData, setData: setInspectionData },
};
const keysToExtract = [
"swtrmt_zone_cd",
"mltv_dtdv_nm",
"fid",
"lyrTypeNm",
"reg_dt",
"mdfcn_dt",
"rgtr",
"mdfr",
];
// const filteredData = Object.entries(featureData)
// .filter(([key]) => keysToExtract.includes(key)) // 필요한 키만 필터링
// .map(([key, value]) => ({ key, value: value ?? "N/A" })); // 테이블 데이터 형식으로 변환
return (
<div>
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}></VerticalTable>
<InputSection title="보수 및 갱신" data={repairData} sectionKey="repair" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<InputSection title="청소 및 준설" data={cleaningData} sectionKey="cleaning" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<InputSection title="점검 및 진단" data={inspectionData} sectionKey="inspection" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<div className="absolute bottom-0 left-0 w-full flex justify-start space-x-2 p-4">
<Button
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
onClick={openModal}
>
Infil
</Button>
<Button
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
onClick={openModal}
>
RDII
</Button>
</div>
<InfilChart btnTxt={btnTxt} isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal} />
</div>
)
return (
<div>
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}></VerticalTable>
<InputSection title="보수 및 갱신" data={repairData} sectionKey="repair" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<InputSection title="청소 및 준설" data={cleaningData} sectionKey="cleaning" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<InputSection title="점검 및 진단" data={inspectionData} sectionKey="inspection" onInputChange={(section, index, field, value) => handleInputChange(section, index, field, value, dataMap)} />
<div className="absolute bottom-0 left-0 w-full flex justify-start space-x-2 p-4">
<Button
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
onClick={openModal}
>
Infil
</Button>
<Button
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
onClick={openModal}
>
RDII
</Button>
</div>
<InfilChart btnTxt={btnTxt} isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal} />
</div>
)
}
export default SwtrmtFclt
@@ -128,7 +128,7 @@ const Ssc = ({ featureData, chkMainSsc, additionalData }: {
body={renderModalBody()}
noHeaderYn='Y'
noFooterYn='N'
// footerContent={renderFooterContent()}
// footerContent={renderFooterContent()}
/>
</div>
}
@@ -6,44 +6,44 @@ import React, { useState } from 'react'
// 하수처리시설
const headList = [
{ id: "title", title: "하수도정비 기본계획보고서" },
{ id: "cad", title: "하수도대장 평면도" },]
{ id: "title", title: "하수도정비 기본계획보고서" },
{ id: "cad", title: "하수도대장 평면도" },]
const modalHeadList = [
{ id: "no", title: "번호" },
{ id: "year", title: "승인연도" },
{ id: "cad", title: '평면도'},
{ id: "fileNm", title: "파일명" },
{ id: "date", title: "입력시간" },
{ id: "no", title: "번호" },
{ id: "year", title: "승인연도" },
{ id: "cad", title: '평면도' },
{ id: "fileNm", title: "파일명" },
{ id: "date", title: "입력시간" },
]
const SwtrmtFclt = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalTitle, setModalTitle] = useState<string>('');
const openModal = (title:string) => {
setIsModalOpen(true);
setModalTitle(title)
};
const closeModal = () => {
setIsModalOpen(false);
};
return (
<div>
<VerticalTable children={{
title: <Button onClick={() => openModal('하수도정비 기본계획보고서')}>보고서</Button>,
cad: <Button onClick={() => openModal('하수도대장 평면도')}>평면도</Button>,
}} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}></VerticalTable>
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
<div className='grid grid-col-2'>
<UiTable headName={modalTitle} tableData={[]} headlist={modalHeadList} pageSize={20} total={0} cellClick={undefined} children={undefined} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}
}></UiTable>
</div>
</Modal>
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalTitle, setModalTitle] = useState<string>('');
const openModal = (title: string) => {
setIsModalOpen(true);
setModalTitle(title)
};
const closeModal = () => {
setIsModalOpen(false);
};
return (
<div>
<VerticalTable children={{
title: <Button onClick={() => openModal('하수도정비 기본계획보고서')}>보고서</Button>,
cad: <Button onClick={() => openModal('하수도대장 평면도')}>평면도</Button>,
}} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}></VerticalTable>
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
<div className='grid grid-col-2'>
<UiTable headName={modalTitle} tableData={[]} headlist={modalHeadList} pageSize={20} total={0} cellClick={undefined} children={undefined} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}
}></UiTable>
</div>
)
</Modal>
</div>
)
}
export default SwtrmtFclt
@@ -3,138 +3,138 @@ import { type ChartConfig, MainChart, CHART_FORM } from "@common/business_compon
import Modal from '@/components/ui/modal';
import { Button } from '@common/components/ui';
type ModalOpenProp = {
isModalOpen: boolean,
setIsModalOpen: (open: boolean) => void,
closeModal: () => void,
btnTxt?: string,
isModalOpen: boolean,
setIsModalOpen: (open: boolean) => void,
closeModal: () => void,
btnTxt?: string,
}
const InfilChart = ({ btnTxt, isModalOpen, setIsModalOpen, closeModal }: ModalOpenProp) => {
const [popupOpen, setPopupOpen] = React.useState(false);
const buttonRef = React.useRef<HTMLButtonElement>(null); // 버튼 참조
const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치
const openPopup = () => {
if (buttonRef.current) {
const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산
setPopupPosition({
top: rect.bottom + window.scrollY, // 버튼 아래쪽
left: rect.left + window.scrollX, // 버튼 왼쪽
});
}
const [popupOpen, setPopupOpen] = React.useState(false);
const buttonRef = React.useRef<HTMLButtonElement>(null); // 버튼 참조
const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치
const openPopup = () => {
if (buttonRef.current) {
const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산
setPopupPosition({
top: rect.bottom + window.scrollY, // 버튼 아래쪽
left: rect.left + window.scrollX, // 버튼 왼쪽
});
}
setPopupOpen(true); // 팝업 열기
};
console.log("btnTxt", btnTxt);
const closePopup = () => { setPopupOpen(false); }; // 팝업 닫기
const chartForm = CHART_FORM.LINE;
const chartData = {
chartData1: [
{ label: "1", val: 20 },
{ label: "3", val: 40 },
{ label: "5", val: 60 },
],
chartData2: [
{ label: "1", val: 15 },
{ label: "3", val: 35 },
{ label: "5", val: 55 },
],
}
const chartConfig = Object.values(chartData).flat().reduce((config, element) => {
config[element.label] = {
label: element.label,
color: "hsl(var(--chart-1))",
};
console.log("btnTxt", btnTxt);
const closePopup = () => {setPopupOpen(false);}; // 팝업 닫기
const chartForm = CHART_FORM.LINE;
const chartData = {
chartData1: [
{ label: "1", val: 20 },
{ label: "3", val: 40 },
{ label: "5", val: 60 },
],
chartData2: [
{ label: "1", val: 15 },
{ label: "3", val: 35 },
{ label: "5", val: 55 },
],
}
const chartConfig = Object.values(chartData).flat().reduce((config, element) => {
config[element.label] = {
label: element.label,
color: "hsl(var(--chart-1))",
};
return config;
}, {} as Record<string, { label: string; color: string }>) satisfies ChartConfig;
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 +1
return `${year}-${month}`;
};
return (
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
{/* 분석 입력현황 조회 */}
<div className='grid grid-cols-4 gap-4'>
<div className="grid grid-rows-4 gap-4 mb-4">
<Button className="bg-white text-white w-[130px] font-bold px-4 mt-2 py-2" onClick={openPopup}>
분석 입력현황 조회
</Button>
return config;
}, {} as Record<string, { label: string; color: string }>) satisfies ChartConfig;
const getCurrentDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 +1
return `${year}-${month}`;
};
return (
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} closeModal={closeModal}>
{/* 분석 입력현황 조회 */}
<div className='grid grid-cols-4 gap-4'>
<div className="grid grid-rows-4 gap-4 mb-4">
<Button className="bg-white text-white w-[130px] font-bold px-4 mt-2 py-2" onClick={openPopup}>
분석 입력현황 조회
</Button>
{/* 침입수량 분석결과 */}
<div className="bg-white text-red-600 font-bold px-4 py-2 ">
침입수량 분석결과
<div className="flex items-center mt-2">
<span className="text-black text-xl font-bold">120</span>
<span className="ml-2 text-black">m³/d</span>
</div>
</div>
</div>
{/* 차트 */}
<div className="w-[500px] h-[300px] bg-white p-4">
<MainChart chartConfig={chartConfig} chartForm={chartForm} chartData={chartData} chartTitle={'관로유량'} />
</div>
{/* 침입수량 분석결과 */}
<div className="bg-white text-red-600 font-bold px-4 py-2 ">
침입수량 분석결과
<div className="flex items-center mt-2">
<span className="text-black text-xl font-bold">120</span>
<span className="ml-2 text-black">m³/d</span>
</div>
{popupOpen && (
<div
className="absolute bg-gray-100 border border-gray-300 w-[320px] shadow-lg p-4 rounded"
style={{
top: popupPosition.top,
left: popupPosition.left,
zIndex: 1000,
}}
>
<div className="text-lg font-bold mb-2">분석 입력현황</div>
<div className="grid grid-cols-2 gap-2">
<div>상수사용량 정보:</div>
<div>
<input type='month' defaultValue={getCurrentDate()} className="border border-gray-300 rounded px-2 py-1"/>
</div>
<div>오수 전환율:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>0.85</option>
<option>0.90</option>
</select>
</div>
<div>수용가-맨홀 매칭:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>매칭(양평).shp</option>
<option>매칭(서울).shp</option>
</select>
</div>
<div>관망도 자료:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>양평.inp</option>
<option>서울.inp</option>
</select>
</div>
{btnTxt === 'RDII' ? <><div>강우 자료:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>R20240815.xlsx</option>
</select>
</div>
<div>RTX 계수 출처:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>2015년 하수도정비기본계획</option>
</select>
</div>
</> : null}
</div>
<div className="flex justify-end mt-4 space-x-2">
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
저장
</button>
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
종료
</button>
</div>
</div>
)}
</Modal>
)
</div>
</div>
{/* 차트 */}
<div className="w-[500px] h-[300px] bg-white p-4">
<MainChart chartConfig={chartConfig} chartForm={chartForm} chartData={chartData} chartTitle={'관로유량'} />
</div>
</div>
{popupOpen && (
<div
className="absolute bg-gray-100 border border-gray-300 w-[320px] shadow-lg p-4 rounded"
style={{
top: popupPosition.top,
left: popupPosition.left,
zIndex: 1000,
}}
>
<div className="text-lg font-bold mb-2">분석 입력현황</div>
<div className="grid grid-cols-2 gap-2">
<div>상수사용량 정보:</div>
<div>
<input type='month' defaultValue={getCurrentDate()} className="border border-gray-300 rounded px-2 py-1" />
</div>
<div>오수 전환율:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>0.85</option>
<option>0.90</option>
</select>
</div>
<div>수용가-맨홀 매칭:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>매칭(양평).shp</option>
<option>매칭(서울).shp</option>
</select>
</div>
<div>관망도 자료:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>양평.inp</option>
<option>서울.inp</option>
</select>
</div>
{btnTxt === 'RDII' ? <><div>강우 자료:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>R20240815.xlsx</option>
</select>
</div>
<div>RTX 계수 출처:</div>
<div>
<select className="border border-gray-300 rounded px-2 py-1">
<option>2015년 하수도정비기본계획</option>
</select>
</div>
</> : null}
</div>
<div className="flex justify-end mt-4 space-x-2">
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
저장
</button>
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
종료
</button>
</div>
</div>
)}
</Modal>
)
}
export default InfilChart
@@ -1,17 +1,17 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "freq", title: "설계빈도", type: "input" },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "freq", title: "설계빈도", type: "input" },
]
// 오수펌프
const sewagePump = ({ featureData }: { featureData: any }) => {
return (
<div><VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={0} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error('Function not implemented.');
}} />
</div>
throw new Error('Function not implemented.');
}} />
</div>
)
}
@@ -2,19 +2,19 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "basu", title: "배수구역", type: "input" },
{ id: "bungu", title: "배수분구", type: "input" },
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
{id:'spec', title:'규격', type:'input'},
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
{ id: "basu", title: "배수구역", type: "input" },
{ id: "bungu", title: "배수분구", type: "input" },
{ id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] },
{ id: 'spec', title: '규격', type: 'input' },
];
const Smh = () => {
return (
<div>
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
<div>
<VerticalTable headName='자체 입력정보' tableData={[]} headlist={inputColumn} pageSize={20} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}></VerticalTable>
</div>
}}></VerticalTable>
</div>
)
}
@@ -1,18 +1,18 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
]
// 빗물펌프
const StormWaterPump = ({ featureData }: { featureData: Record<string, any> }) => {
const validKey = inputColumn.map((item) => item.id);
const filteredData = featureData && Object.entries(featureData).filter(([key]) => validKey.includes(key)).reduce((obj: Record<string, any>, [key, value]) => {obj[key] = value; return obj;}, {});
const filteredData = featureData && Object.entries(featureData).filter(([key]) => validKey.includes(key)).reduce((obj: Record<string, any>, [key, value]) => { obj[key] = value; return obj; }, {});
return (
<div><VerticalTable headName='자체 입력정보' tableData={[filteredData]} headlist={inputColumn} pageSize={0} total={0} children={undefined} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void {
throw new Error('Function not implemented.');
}} />
</div>
throw new Error('Function not implemented.');
}} />
</div>
)
}
@@ -9,202 +9,202 @@ import { getLayerContentComponent } from "./components/common";
import { SwtrmtFcltCol, SwtrmtPrcsZoneCol, untrtSwgRainCol1, untrtSwgRainCol2 } from "./components/common/table";
// TODO:
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
// const featureId = featureData.getId(); // 피처의 id 가져오기
const featureId = typeof featureData.getId === 'function'
? featureData.getId()
: featureData.fid;
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [buttonChk, setButtonChk] = useState(true)
const [fetchData, setfetchData] = useState<any>(); // api 호출여부
const contentRef = useRef<HTMLDivElement>(null);
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
// const featureId = featureData.getId(); // 피처의 id 가져오기
const featureId = typeof featureData.getId === 'function'
? featureData.getId()
: featureData.fid;
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [buttonChk, setButtonChk] = useState(true)
const [fetchData, setfetchData] = useState<any>(); // api 호출여부
const contentRef = useRef<HTMLDivElement>(null);
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
const getSelectedLayers = () => {
let result = null;
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
featureLayer = layer; // 피처가 속한 레이어 찾기
}
});
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
if (featureLayer.get('lyrNm') === null) {
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실';
}
else result = featureLayer.get('lyrNm');
return result;
// return featureLayer.get('lyrNm'); // 레이어 이름 출력
};
useEffect(() => {
const fetchData = async () => {
if (featureId) {
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
setLayerNm(selectedLayerName); // 상태 업데이트
const layerId = featureId.split('.')[0]
let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`);
setfetchData(data)
switch (selectedLayerName) {
case '하수처리시설':
setHeadlist(SwtrmtFcltCol);
setButtonChk(false)
break;
case '하수처리구역':
setHeadlist(SwtrmtPrcsZoneCol)
setButtonChk(false)
break;
case '중점우수토실':
setHeadlist(untrtSwgRainCol1)
setHeadlist2(untrtSwgRainCol2)
setButtonChk(false)
setChkMainSsc(true)
// 데이터 전처리: 합쳐진 필드 추가
data = data ? {
...data,
discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`
} : {};
// 수량/수질측정 정보 데이터 호출
const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`);
setMntrngsData(additionalResponse.content);
break;
case '우수토실':
setButtonChk(true)
break;
case '하수관로':
setButtonChk(true)
break;
case '오수펌프':
setButtonChk(true)
break;
case '빗물펌프':
setButtonChk(true)
break;
case '하수맨홀':
setButtonChk(true)
break;
default:
break;
}
setTableData(data);
};
}
fetchData();
}, [featureId]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
};
const handleModalSave = () => {
setIsModalOpen(false); // 모달 닫기
btnClickEvent(); // 저장 이벤트 실행
};
const handleModalClose = () => {
setIsModalOpen(false); // 모달 닫기
};
const btnClickEvent = async () => {
const bodyData: Record<string, any> = {}
if (buttonChk && contentRef.current) {
const inputElements = contentRef.current.querySelectorAll('input');
const selectElements = contentRef.current.querySelectorAll('select');
// 카멜케이스 변환 함수
const toCamelCase = (str: string) =>
str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
// select 태그의 값 가져오기
selectElements.forEach((select) => {
const camelCaseId = toCamelCase(select.id);
bodyData[camelCaseId] = select.value;
});
// input 태그의 값 가져오기
inputElements.forEach((input) => {
const camelCaseId = toCamelCase(input.id);
bodyData[camelCaseId] = input.value;
});
if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '하수맨홀') bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl'
bodyData['fid'] = featureId
const data = await putData(`v1/fclts/${featureId}`, bodyData);
if (data.status == 200) {
alert('저장되었습니다')
setTableData(data)
setfetchData(data)
}
}
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
const getSelectedLayers = () => {
let result = null;
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
featureLayer = layer; // 피처가 속한 레이어 찾기
}
});
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
if (featureLayer.get('lyrNm') === null) {
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실';
}
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) : null;
return (
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[50]">
<div className="card-header bg-primary flex items-center justify-between p-4">
<div className="flex mx-3 flex-col">
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
{/* <div className="font-medium text-white text-label">(00구 00리 하수관로)</div> */}
</div>
<button
onClick={onClose}
className="text-white hover:text-gray-700 focus:outline-none"
aria-label="닫기"
>
✕
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden flex-1" ref={contentRef}>
<VerticalTable
headName='LX 연계정보'
tableName1={layerNm === '중점우수토실' ? "측정지점" : undefined}
tableData={[tableData]}
headlist={headlist}
pageSize={20}
total={0}
children={undefined}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}
// 두 번째 테이블 조건부 props
showSecondTable={layerNm === '중점우수토실' ? true : false}
tableName2={layerNm === '중점우수토실' ? "시설현황" : undefined}
tableData2={layerNm === '중점우수토실' && tableData ? [tableData] : []}
headlist2={layerNm === '중점우수토실' ? headlist2 : undefined}
pageSize2={20}
total2={0}
isEditable2={buttonChk}
/>
<div className="w-full mx-5 flex items-center justify-end space-x-2">
{buttonChk ? <Button onClick={handleSaveClick} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
저장
</Button> : null}
</div>
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
{ContentComponent &&
<ContentComponent
featureData={fetchData}
chkMainSsc={chkMainSsc}
additionalData={mntrngsData}
/>
}
</div>
else result = featureLayer.get('lyrNm');
return result;
// return featureLayer.get('lyrNm'); // 레이어 이름 출력
};
useEffect(() => {
const fetchData = async () => {
if (featureId) {
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
setLayerNm(selectedLayerName); // 상태 업데이트
const layerId = featureId.split('.')[0]
let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`);
setfetchData(data)
switch (selectedLayerName) {
case '하수처리시설':
setHeadlist(SwtrmtFcltCol);
setButtonChk(false)
break;
case '하수처리구역':
setHeadlist(SwtrmtPrcsZoneCol)
setButtonChk(false)
break;
case '중점우수토실':
setHeadlist(untrtSwgRainCol1)
setHeadlist2(untrtSwgRainCol2)
setButtonChk(false)
setChkMainSsc(true)
// 데이터 전처리: 합쳐진 필드 추가
data = data ? {
...data,
discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`
} : {};
// 수량/수질측정 정보 데이터 호출
const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`);
setMntrngsData(additionalResponse.content);
break;
case '우수토실':
setButtonChk(true)
break;
case '하수관로':
setButtonChk(true)
break;
case '오수펌프':
setButtonChk(true)
break;
case '빗물펌프':
setButtonChk(true)
break;
case '하수맨홀':
setButtonChk(true)
break;
default:
break;
}
setTableData(data);
};
}
fetchData();
}, [featureId]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
};
const handleModalSave = () => {
setIsModalOpen(false); // 모달 닫기
btnClickEvent(); // 저장 이벤트 실행
};
const handleModalClose = () => {
setIsModalOpen(false); // 모달 닫기
};
const btnClickEvent = async () => {
const bodyData: Record<string, any> = {}
if (buttonChk && contentRef.current) {
const inputElements = contentRef.current.querySelectorAll('input');
const selectElements = contentRef.current.querySelectorAll('select');
// 카멜케이스 변환 함수
const toCamelCase = (str: string) =>
str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
// select 태그의 값 가져오기
selectElements.forEach((select) => {
const camelCaseId = toCamelCase(select.id);
bodyData[camelCaseId] = select.value;
});
// input 태그의 값 가져오기
inputElements.forEach((input) => {
const camelCaseId = toCamelCase(input.id);
bodyData[camelCaseId] = input.value;
});
if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '하수맨홀') bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl'
bodyData['fid'] = featureId
const data = await putData(`v1/fclts/${featureId}`, bodyData);
if (data.status == 200) {
alert('저장되었습니다')
setTableData(data)
setfetchData(data)
}
}
}
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) : null;
return (
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[50]">
<div className="card-header bg-primary flex items-center justify-between p-4">
<div className="flex mx-3 flex-col">
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
{/* <div className="font-medium text-white text-label">(00구 00리 하수관로)</div> */}
</div>
)
<button
onClick={onClose}
className="text-white hover:text-gray-700 focus:outline-none"
aria-label="닫기"
>
✕
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden flex-1" ref={contentRef}>
<VerticalTable
headName='LX 연계정보'
tableName1={layerNm === '중점우수토실' ? "측정지점" : undefined}
tableData={[tableData]}
headlist={headlist}
pageSize={20}
total={0}
children={undefined}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}
// 두 번째 테이블 조건부 props
showSecondTable={layerNm === '중점우수토실' ? true : false}
tableName2={layerNm === '중점우수토실' ? "시설현황" : undefined}
tableData2={layerNm === '중점우수토실' && tableData ? [tableData] : []}
headlist2={layerNm === '중점우수토실' ? headlist2 : undefined}
pageSize2={20}
total2={0}
isEditable2={buttonChk}
/>
<div className="w-full mx-5 flex items-center justify-end space-x-2">
{buttonChk ? <Button onClick={handleSaveClick} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
저장
</Button> : null}
</div>
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
{ContentComponent &&
<ContentComponent
featureData={fetchData}
chkMainSsc={chkMainSsc}
additionalData={mntrngsData}
/>
}
</div>
</div>
)
}
export default FeatureClickEventPopup;
+232 -232
View File
@@ -10,55 +10,55 @@ import { useEffect, useState, type ReactNode } from 'react'
import LoadingOverlay from '@/components/ui/LoadingBar'
type TitleName = {
mainTitle: string
firstCardTitle: string
secondCardTitle?: string
mainTitle: string
firstCardTitle: string
secondCardTitle?: string
}
type SelectBox = {
labelNm?: Label
searchChk?: boolean
title?: string;
onSearchClick?: () => Promise<void>;
labelNm?: Label
searchChk?: boolean
title?: string;
onSearchClick?: () => Promise<void>;
}
type Label = {
first: string
second: string
third: string
fourth: string
first: string
second: string
third: string
fourth: string
}
// API로부터 받아올 데이터 타입을 정의 (SelectBox에 사용될 형태)
interface ApiSelectOption {
text: string;
val: string;
text: string;
val: string;
}
type BaseLayout = {
children?: ReactNode[]
titleName: TitleName
form: any
onSubmitEvent: Function
graphBtn?: boolean
analysisBtn?: string
firstDivBtn?: boolean
selectBox?: SelectBox
showDatePicker?: boolean;
enoCd?: string | number
setEnoCd?: (enoCd: string) => void
ctpvCd?: string | number
setCtpvCd?: (ctpvCd: string) => void
sigungu?: ApiSelectOption
setSigungu?: (sigungu: ApiSelectOption) => void
selectedSwageTreatmentPlant?: string | number;
onSwageTreatmentPlantChange?: (value: string | number) => void;
selectedSwageProcessingZone?: string | number;
onSwageProcessingZoneChange?: (value: string | number) => void;
aplcnYm?: Date
setAplcnYm?: (aplcnYm: Date | undefined) => void;
isLoading?: boolean;
// Excel 다운로드 관련 props 추가
onExcelDownload?: () => void;
onDeleteFile?: () => void;
onAnalysis?: () => void;
selectedRowData?: any;
children?: ReactNode[]
titleName: TitleName
form: any
onSubmitEvent: Function
graphBtn?: boolean
analysisBtn?: string
firstDivBtn?: boolean
selectBox?: SelectBox
showDatePicker?: boolean;
enoCd?: string | number
setEnoCd?: (enoCd: string) => void
ctpvCd?: string | number
setCtpvCd?: (ctpvCd: string) => void
sigungu?: ApiSelectOption
setSigungu?: (sigungu: ApiSelectOption) => void
selectedSwageTreatmentPlant?: string | number;
onSwageTreatmentPlantChange?: (value: string | number) => void;
selectedSwageProcessingZone?: string | number;
onSwageProcessingZoneChange?: (value: string | number) => void;
aplcnYm?: Date
setAplcnYm?: (aplcnYm: Date | undefined) => void;
isLoading?: boolean;
// Excel 다운로드 관련 props 추가
onExcelDownload?: () => void;
onDeleteFile?: () => void;
onAnalysis?: () => void;
selectedRowData?: any;
}
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
/**
@@ -71,207 +71,207 @@ type BaseLayout = {
* @returns
*/
const BaseLayout = ({
children,
titleName,
form,
onSubmitEvent,
graphBtn = false,
analysisBtn,
selectBox,
showDatePicker = false,
enoCd,
setEnoCd,
ctpvCd,
setCtpvCd,
sigungu,
setSigungu,
selectedSwageTreatmentPlant,
onSwageTreatmentPlantChange,
selectedSwageProcessingZone,
onSwageProcessingZoneChange,
aplcnYm,
setAplcnYm,
isLoading,
// Excel 다운로드 관련 props 추가
onExcelDownload,
onDeleteFile,
onAnalysis,
selectedRowData
children,
titleName,
form,
onSubmitEvent,
graphBtn = false,
analysisBtn,
selectBox,
showDatePicker = false,
enoCd,
setEnoCd,
ctpvCd,
setCtpvCd,
sigungu,
setSigungu,
selectedSwageTreatmentPlant,
onSwageTreatmentPlantChange,
selectedSwageProcessingZone,
onSwageProcessingZoneChange,
aplcnYm,
setAplcnYm,
isLoading,
// Excel 다운로드 관련 props 추가
onExcelDownload,
onDeleteFile,
onAnalysis,
selectedRowData
}: BaseLayout) => {
const defaultOption: ApiSelectOption = { val: '0', text: '선택' };
const defaultOption: ApiSelectOption = { val: '0', text: '선택' };
const [processingZoneOptions, setProcessingZoneOptions] = useState<ApiSelectOption[]>([]);
const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState<ApiSelectOption[]>([]);
const [isLoadingAreaData, setIsLoadingAreaData] = useState<boolean>(isLoading || false);
const [atflId, setAtflId] = useState('')
const [fileNm, setFileNm] = useState('')
const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('')
const [processingZoneOptions, setProcessingZoneOptions] = useState<ApiSelectOption[]>([]);
const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState<ApiSelectOption[]>([]);
const [isLoadingAreaData, setIsLoadingAreaData] = useState<boolean>(isLoading || false);
const [atflId, setAtflId] = useState('')
const [fileNm, setFileNm] = useState('')
const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('')
useEffect(() => {
const fetchData = async () => {
if (!sigungu || sigungu.val === '0' || !sigungu.val) {
console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
setIsLoadingAreaData(false);
return;
}
useEffect(() => {
const fetchData = async () => {
if (!sigungu || sigungu.val === '0' || !sigungu.val) {
console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
setIsLoadingAreaData(false);
return;
}
setIsLoadingAreaData(true);
try {
const queryParamsZone = new URLSearchParams({
lyrTypeNm: 'tb_ms_swage_prcs_zone',
page: '0',
size: '100000',
sort: 'string',
});
const cqlFiltersZone: string[] = [];
if (sigungu.val && sigungu.val !== '0') {
// console.log("시군구 코드", sigungu.val);
cqlFiltersZone.push(`sgg_cd='${sigungu.val}'`);
}
if (cqlFiltersZone.length > 0) {
queryParamsZone.append('cqlFiter', cqlFiltersZone.join(' and '));
}
const queryParamsFacility = new URLSearchParams({
lyrTypeNm: 'tb_il_swage_prcs_fclt',
page: '0',
size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능
sort: 'string',
});
const cqlFiltersFacility: string[] = [];
if (sigungu.text && sigungu.val !== '0') {
cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`);
}
if (cqlFiltersFacility.length > 0) {
queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter
}
console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`);
console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
const [zoneRes, facilityRes] = await Promise.all([
getData(`v1/fclts?${queryParamsZone.toString()}`),
getData(`v1/fclts?${queryParamsFacility.toString()}`)
]);
const zones = (zoneRes.data || []).map((item: any) => ({
text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`,
val: item.fid || item.id || item.gid || item.objectid
}));
setProcessingZoneOptions([defaultOption, ...zones]);
console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]);
const facilities = (facilityRes.data || []).map((item: any) => ({
text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`,
val: item.fid || item.id || item.gid || item.objectid
}));
setTreatmentFacilityOptions([defaultOption, ...facilities]);
console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]);
} catch (error) {
console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
} finally {
setIsLoadingAreaData(false);
}
};
fetchData();
}, [sigungu]); // sigungu 상태가 변경될 때마다 실행
// 파일 다운로드
const isChckFile = () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onExcelDownload) {
onExcelDownload();
return;
setIsLoadingAreaData(true);
try {
const queryParamsZone = new URLSearchParams({
lyrTypeNm: 'tb_ms_swage_prcs_zone',
page: '0',
size: '100000',
sort: 'string',
});
const cqlFiltersZone: string[] = [];
if (sigungu.val && sigungu.val !== '0') {
// console.log("시군구 코드", sigungu.val);
cqlFiltersZone.push(`sgg_cd='${sigungu.val}'`);
}
return true
}
// 파일 삭제
const isDeleteFile = async () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onDeleteFile) {
onDeleteFile();
return;
if (cqlFiltersZone.length > 0) {
queryParamsZone.append('cqlFiter', cqlFiltersZone.join(' and '));
}
}
return (
<Form {...form} className="min-h-screen flex flex-col">
<LoadingOverlay isLoading={isLoading ?? false} />
<ListDiv>
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
{selectBox?.title && (
<div className="card-header">
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
</div>
)}
<div className="flex items-center w-full">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="ml-4 w-20 text-xl text-gray-600 whitespace-nowrap">분석기간</Label>
<DatePicker
mode={"single"}
field={{
value: aplcnYm ?? new Date(),
onChange: setAplcnYm ?? ((date: Date | undefined) => console.log("DatePicker onChange:", date))
}}
selectClass="ml-5 date-picker-trigger" />
</>
)}
<GovSidoSigunguSearchForm
isShowPart={true}
setRtnEnvPartValue={(val) => setEnoCd?.(val)}
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
>
<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>
{selectBox?.searchChk ? <SearchFormRight><Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button></SearchFormRight> : null}
</div>
</ListDiv>
<ListDiv>
<div className="card-header">
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
<div>
{graphBtn ? <Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile}>파일 다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile}>삭제</Button> : null}
{/* 분석 버튼 */}
{analysisBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={onAnalysis}>{analysisBtn}</Button> : null}
</div>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmitEvent)} className="flex-grow flex flex-col">
<div className="card !m-[1.2rem] flex-grow flex flex-col">
{/* 폼 정보 입력 칸 */}
{children?.[0]}
</div>
</form>
</Form>
</ListDiv>
{
children?.[1] != null ? <ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">{titleName?.secondCardTitle}</h3>
<div>
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">파일 다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">삭제</Button> : null}
</div>
</div>
<div className="card !m-[1.2rem]">{children?.[1]}</div>
</ListDiv> : null
}
const queryParamsFacility = new URLSearchParams({
lyrTypeNm: 'tb_il_swage_prcs_fclt',
page: '0',
size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능
sort: 'string',
});
const cqlFiltersFacility: string[] = [];
if (sigungu.text && sigungu.val !== '0') {
cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`);
}
if (cqlFiltersFacility.length > 0) {
queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter
}
console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`);
console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
const [zoneRes, facilityRes] = await Promise.all([
getData(`v1/fclts?${queryParamsZone.toString()}`),
getData(`v1/fclts?${queryParamsFacility.toString()}`)
]);
const zones = (zoneRes.data || []).map((item: any) => ({
text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`,
val: item.fid || item.id || item.gid || item.objectid
}));
setProcessingZoneOptions([defaultOption, ...zones]);
console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]);
const facilities = (facilityRes.data || []).map((item: any) => ({
text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`,
val: item.fid || item.id || item.gid || item.objectid
}));
setTreatmentFacilityOptions([defaultOption, ...facilities]);
console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]);
} catch (error) {
console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
} finally {
setIsLoadingAreaData(false);
}
};
fetchData();
}, [sigungu]); // sigungu 상태가 변경될 때마다 실행
// 파일 다운로드
const isChckFile = () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onExcelDownload) {
onExcelDownload();
return;
}
return true
}
// 파일 삭제
const isDeleteFile = async () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onDeleteFile) {
onDeleteFile();
return;
}
}
return (
<Form {...form} className="min-h-screen flex flex-col">
<LoadingOverlay isLoading={isLoading ?? false} />
<ListDiv>
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
{selectBox?.title && (
<div className="card-header">
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
</div>
)}
<div className="flex items-center w-full">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="ml-4 w-20 text-xl text-gray-600 whitespace-nowrap">분석기간</Label>
<DatePicker
mode={"single"}
field={{
value: aplcnYm ?? new Date(),
onChange: setAplcnYm ?? ((date: Date | undefined) => console.log("DatePicker onChange:", date))
}}
selectClass="ml-5 date-picker-trigger" />
</>
)}
<GovSidoSigunguSearchForm
isShowPart={true}
setRtnEnvPartValue={(val) => setEnoCd?.(val)}
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
>
<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>
{selectBox?.searchChk ? <SearchFormRight><Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button></SearchFormRight> : null}
</div>
</ListDiv>
<ListDiv>
<div className="card-header">
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
<div>
{graphBtn ? <Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile}>파일 다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile}>삭제</Button> : null}
{/* 분석 버튼 */}
{analysisBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={onAnalysis}>{analysisBtn}</Button> : null}
</div>
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmitEvent)} className="flex-grow flex flex-col">
<div className="card !m-[1.2rem] flex-grow flex flex-col">
{/* 폼 정보 입력 칸 */}
{children?.[0]}
</div>
</form>
</Form>
)
</ListDiv>
{
children?.[1] != null ? <ListDiv>
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">{titleName?.secondCardTitle}</h3>
<div>
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">파일 다운로드</Button> : null}
{/* 그래프 있을 시 */}
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">삭제</Button> : null}
</div>
</div>
<div className="card !m-[1.2rem]">{children?.[1]}</div>
</ListDiv> : null
}
</Form>
)
}
export default BaseLayout
@@ -13,358 +13,358 @@ import { excelToFlowRateData, excelToMatchingResultData, excelToTimePatternData
import BaseLayout from '../BaseLayout'
const formSchema = z.object({
managenm: z.string(),
region: z.string(),
suyeonga: z.number(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
suyeongaPlace: z.string(),
pattern: z.string()
managenm: z.string(),
region: z.string(),
suyeonga: z.number(),
inputdate: z.coerce.date(),
rtkSource: z.string(),
suyeongaPlace: z.string(),
pattern: z.string()
})
// 각 파일 업로드 폼의 데이터를 저장할 인터페이스 정의
interface UploadedFileInfo {
file: File | null;
atflCd: string | null;
atflId: string | null;
// 필요하다면 파일 이름 등 추가 정보 저장 가능
// fileName: string;
file: File | null;
atflCd: string | null;
atflId: string | null;
// 필요하다면 파일 이름 등 추가 정보 저장 가능
// fileName: string;
}
// 초기 파일 정보 상태
const initialFileInfo: UploadedFileInfo = { file: null, atflCd: null, atflId: null };
interface ApiSelectOption {
text: string;
val: string;
text: string;
val: string;
}
const IrSetting = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
}
})
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
}
})
const [uploadedFilesData, setUploadedFilesData] = useState<UploadedFileInfo[]>([
initialFileInfo, // 0: 오수관망도
initialFileInfo, // 1: 수용가-맨홀 매칭결과
initialFileInfo, // 2: 오수량 시간패턴
initialFileInfo // 3: 유량자료 등록
const [uploadedFilesData, setUploadedFilesData] = useState<UploadedFileInfo[]>([
initialFileInfo, // 0: 오수관망도
initialFileInfo, // 1: 수용가-맨홀 매칭결과
initialFileInfo, // 2: 오수량 시간패턴
initialFileInfo // 3: 유량자료 등록
]);
const [selectedDate, setSelectedDate] = useState(new Date());
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
const [select1, setSelect1] = useState('0'); // 환경청코드
const [select2, setSelect2] = useState('0'); // 시도코드
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
// 폼 초기화 함수
const resetAllForms = () => {
form.reset({ // react-hook-form 초기화
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
});
setUploadedFilesData([ // 파일 업로드 상태 초기화
initialFileInfo,
initialFileInfo,
initialFileInfo,
initialFileInfo
]);
setSelectedDate(new Date()); // 날짜 선택 초기화
setInput1(''); // 입력 필드 초기화
setInput2(''); // 입력 필드 초기화
setSelect1('0'); // 선택 필드 초기화
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4(0);
setSelect5(0);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
};
const [selectedDate, setSelectedDate] = useState(new Date());
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
const [select1, setSelect1] = useState('0'); // 환경청코드
const [select2, setSelect2] = useState('0'); // 시도코드
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
// 폼 초기화 함수
const resetAllForms = () => {
form.reset({ // react-hook-form 초기화
managenm: "",
region: "",
suyeonga: 0,
inputdate: new Date(),
rtkSource: "",
suyeongaPlace: "",
pattern: ""
});
setUploadedFilesData([ // 파일 업로드 상태 초기화
initialFileInfo,
initialFileInfo,
initialFileInfo,
initialFileInfo
]);
setSelectedDate(new Date()); // 날짜 선택 초기화
setInput1(''); // 입력 필드 초기화
setInput2(''); // 입력 필드 초기화
setSelect1('0'); // 선택 필드 초기화
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4(0);
setSelect5(0);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
const handleFileSelection = (
index: number, // FileUploadForm의 fileIdx 값
selectedFile: File | null,
cd: string | null,
id: string | null
) => {
setUploadedFilesData(prevData => {
const newData = [...prevData];
newData[index] = {
file: selectedFile,
atflCd: cd,
atflId: id,
};
return newData;
});
console.log(`Page: File data received for form index ${index}`, { fileName: selectedFile?.name, cd, id });
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
const handleFileSelection = (
index: number, // FileUploadForm의 fileIdx 값
selectedFile: File | null,
cd: string | null,
id: string | null
) => {
setUploadedFilesData(prevData => {
const newData = [...prevData];
newData[index] = {
file: selectedFile,
atflCd: cd,
atflId: id,
};
return newData;
});
console.log(`Page: File data received for form index ${index}`, { fileName: selectedFile?.name, cd, id });
};
const registFile = async (analysisData: any) => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log("Submitting combined analysis data to backend:", analysisData);
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/inftwtAnls', analysisData);
console.log("Response from backend:", data);
if (data) {
setIsLoadingResult(false); // 로딩 상태 시작
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
} else {
setIsLoadingResult(false); // 로딩 상태 시작
alert("분석 데이터 제출 중 오류가 발생했습니다.");
}
};
const registFile = async (analysisData: any) => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log("Submitting combined analysis data to backend:", analysisData);
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/inftwtAnls', analysisData);
console.log("Response from backend:", data);
if (data) {
setIsLoadingResult(false); // 로딩 상태 시작
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
} else {
setIsLoadingResult(false); // 로딩 상태 시작
alert("분석 데이터 제출 중 오류가 발생했습니다.");
}
};
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
const file3 = uploadedFilesData[2]; // 오수량 시간패턴
const file4 = uploadedFilesData[3]; // 유량자료
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
const file3 = uploadedFilesData[2]; // 오수량 시간패턴
const file4 = uploadedFilesData[3]; // 유량자료
console.log("오수관망도 정보:", file1?.file);
console.log("수용가-맨홀 매칭결과 정보:", file2);
console.log("오수량 시간패턴 정보:", file3);
console.log("유량자료 정보:", file4);
console.log("오수관망도 정보:", file1?.file);
console.log("수용가-맨홀 매칭결과 정보:", file2);
console.log("오수량 시간패턴 정보:", file3);
console.log("유량자료 정보:", file4);
// 모든 필수 파일이 선택되었는지 확인 (UploadedFileInfo 객체와 그 안의 file 객체 모두 확인)
// 유량자료는 선택값
if (!file1?.file || !file2?.file || !file3?.file) {
setIsLoadingResult(false);
alert('모든 분석용 파일을 선택해주세요.');
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; };
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(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null;
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
console.warn(`Warning: filePicked returned undefined for ${file.file.name}`);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null
};
}
// jsonData를 각 parser 함수로 처리
const parsedDataBySpecificFunction = parser(jsonData);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: parsedDataBySpecificFunction
};
})
);
// 각 파싱 결과 접근
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 = file4?.file
? parsedResults.find(r => r && r.fileName === file4.file?.name)?.parsedData
: null;
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name, // 오수관망도 파일명
atflHrPttnNm: file3?.file?.name, // 오수량 시간패턴 파일명
atflMpngNm: file2?.file?.name, // 수용가-맨홀 매칭결과 파일명
atflOilqNm: file4?.file?.name || null, // 유량자료 파일명 (없을 수 있음)
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnHrPttn: timePatternData, // 오수량 시간패턴
tbSnOilqData: flowRateData, // 유량자료 (없을 수 있음)
};
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
// 수정된 registFile 함수를 호출하여 combinedAnalysisData를 전송합니다.
await registFile(combinedAnalysisData);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("파일 파싱 중 오류 발생:", error);
alert("파일 처리 중 오류가 발생했습니다.");
return;
}
// 모든 필수 파일이 선택되었는지 확인 (UploadedFileInfo 객체와 그 안의 file 객체 모두 확인)
// 유량자료는 선택값
if (!file1?.file || !file2?.file || !file3?.file) {
setIsLoadingResult(false);
alert('모든 분석용 파일을 선택해주세요.');
return;
}
return (
<>
<BaseLayout
titleName={{ mainTitle: '침입수 분석', firstCardTitle: '침입수 분석 자료 입력' }}
form={form}
onSubmitEvent={() => { }}
graphBtn={false}
analysisBtn={'침입수분석 실행'}
selectBox={{ title: '분석 대상지역 선택' }}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div key="irsetting-layout-key" className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='▷ 오수관망도'>
<FileUploadForm
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
showButton={false}
showPreview={true}
accept={""}
fileIdx={0} />
</FormSection>
<FormSection name='suyeonga' form={form} titleNm='▷ 수용가-맨홀 매칭결과'>
<FileUploadForm
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={1} />
</FormSection>
<FormSection name='pattern' form={form} titleNm='▷ 오수량 시간패턴'>
<FileUploadForm
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</FormSection>
<FormSection
name='inputdate'
form={form}
titleNm='▷ 상수사용량 적용기간'>
<DatePicker
mode={"single"}
field={{
value: selectedDate, // 현재 페이지의 selectedDate 상태 사용
onChange: (date: Date | undefined) => {
if (date) {
setSelectedDate(date);
form.setValue('inputdate', date, { shouldValidate: true });
}
}
}}
selectClass="ml-5 date-picker-trigger" // date-picker.css의 스타일 적용
/>
</FormSection>
<FormSection
name='rtkSource'
form={form}
titleNm='▷ 오수전환율'>
<Input
value={input1}
onChange={(e) => {
const newValue = e.target.value;
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; };
if (newValue === "") {
setInput1(newValue);
return;
}
const filePickers: {
file: UploadedFileInfo;
parser: (excelData: any) => MatchingResult[] | TimePattern[] | FlowRate[];
}[] = [
{ file: file2, parser: excelToMatchingResultData },
{ file: file3, parser: excelToTimePatternData }
];
// 숫자와 '.'만 허용하고, '.'은 최대 하나인지 확인
const isValidChars = /^[0-9]*\.?[0-9]*$/.test(newValue);
if (!isValidChars) return; // 유효하지 않은 문자 또는 여러 개의 '.' 포함
if (file4?.file) {
filePickers.push({ file: file4, parser: excelToFlowRateData });
}
// '.' 기호가 포함된 경우, 처음이나 끝에 오면 안 됨
if (newValue.includes('.') && newValue.startsWith('.')) return; // '.'이 처음에는 올 수 없음
try {
const parsedResults = await Promise.all(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null;
setInput1(newValue);
}}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]"
placeholder="예: 0.75" />
</FormSection>
<FormSection
name='analysis information'
form={form}
titleNm='▷ 분석 정보'>
<Input
value={input2}
onChange={(e) => setInput2(e.target.value)}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]" />
</FormSection>
<ListDiv>
<div className="card !m-[1.2rem]">
<FormSection name='region' form={form} titleNm='▷ 유량자료 등록'>
<FileUploadForm
key={`file-upload-3-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(3, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={3}
/>
</FormSection>
</div>
</ListDiv>
</div>]}
{/* <div className="flex items-center gap-4">
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
console.warn(`Warning: filePicked returned undefined for ${file.file.name}`);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null
};
}
// jsonData를 각 parser 함수로 처리
const parsedDataBySpecificFunction = parser(jsonData);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: parsedDataBySpecificFunction
};
})
);
// 각 파싱 결과 접근
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 = file4?.file
? parsedResults.find(r => r && r.fileName === file4.file?.name)?.parsedData
: null;
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name, // 오수관망도 파일명
atflHrPttnNm: file3?.file?.name, // 오수량 시간패턴 파일명
atflMpngNm: file2?.file?.name, // 수용가-맨홀 매칭결과 파일명
atflOilqNm: file4?.file?.name || null, // 유량자료 파일명 (없을 수 있음)
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnHrPttn: timePatternData, // 오수량 시간패턴
tbSnOilqData: flowRateData, // 유량자료 (없을 수 있음)
};
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
// 수정된 registFile 함수를 호출하여 combinedAnalysisData를 전송합니다.
await registFile(combinedAnalysisData);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("파일 파싱 중 오류 발생:", error);
alert("파일 처리 중 오류가 발생했습니다.");
return;
}
}
return (
<>
<BaseLayout
titleName={{ mainTitle: '침입수 분석', firstCardTitle: '침입수 분석 자료 입력' }}
form={form}
onSubmitEvent={() => { }}
graphBtn={false}
analysisBtn={'침입수분석 실행'}
selectBox={{ title: '분석 대상지역 선택' }}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div key="irsetting-layout-key" className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='▷ 오수관망도'>
<FileUploadForm
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
showButton={false}
showPreview={true}
accept={""}
fileIdx={0} />
</FormSection>
<FormSection name='suyeonga' form={form} titleNm='▷ 수용가-맨홀 매칭결과'>
<FileUploadForm
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={1} />
</FormSection>
<FormSection name='pattern' form={form} titleNm='▷ 오수량 시간패턴'>
<FileUploadForm
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</FormSection>
<FormSection
name='inputdate'
form={form}
titleNm='▷ 상수사용량 적용기간'>
<DatePicker
mode={"single"}
field={{
value: selectedDate, // 현재 페이지의 selectedDate 상태 사용
onChange: (date: Date | undefined) => {
if (date) {
setSelectedDate(date);
form.setValue('inputdate', date, { shouldValidate: true });
}
}
}}
selectClass="ml-5 date-picker-trigger" // date-picker.css의 스타일 적용
/>
</FormSection>
<FormSection
name='rtkSource'
form={form}
titleNm='▷ 오수전환율'>
<Input
value={input1}
onChange={(e) => {
const newValue = e.target.value;
if (newValue === "") {
setInput1(newValue);
return;
}
// 숫자와 '.'만 허용하고, '.'은 최대 하나인지 확인
const isValidChars = /^[0-9]*\.?[0-9]*$/.test(newValue);
if (!isValidChars) return; // 유효하지 않은 문자 또는 여러 개의 '.' 포함
// '.' 기호가 포함된 경우, 처음이나 끝에 오면 안 됨
if (newValue.includes('.') && newValue.startsWith('.')) return; // '.'이 처음에는 올 수 없음
setInput1(newValue);
}}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]"
placeholder="예: 0.75" />
</FormSection>
<FormSection
name='analysis information'
form={form}
titleNm='▷ 분석 정보'>
<Input
value={input2}
onChange={(e) => setInput2(e.target.value)}
className="ml-5 h-[3.2rem] text-center text-xl w-[150px]" />
</FormSection>
<ListDiv>
<div className="card !m-[1.2rem]">
<FormSection name='region' form={form} titleNm='▷ 유량자료 등록'>
<FileUploadForm
key={`file-upload-3-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(3, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={3}
/>
</FormSection>
</div>
</ListDiv>
</div>]}
{/* <div className="flex items-center gap-4">
<FormSection
name='region' form={form} titleNm='▷ 유량자료 등록'>
<FileUploadForm
@@ -379,9 +379,9 @@ const IrSetting = () => {
<Button size="sm" type="submit" onClick={handleAnalysisClick}>침입수분석 실행</Button>
</div>
</div> */}
</BaseLayout>
</>
)
</BaseLayout>
</>
)
}
export default IrSetting
+378 -378
View File
@@ -17,393 +17,393 @@ import { EditableFormTable, type EditableTableHeadItem } from '../../../componen
// zod 스키마 정의
const formSchema = z.object({
enoCd: z.string(), // 환경청코드
ctpvCd: z.string(), // 시도코드
sggCd: z.string(), // 시군구코드
swageTmplntCd: z.string(), // 하수처리장코드
swtrmtZoneCd: z.string(), // 하수처리구역코드
rtkSource: z.string(), // RTK계수 출처
rdiiArea: z.string(), // RDII 기여면적
unitFactors: z.array(z.object({ // 단위도 계수
response: z.string(),
R: z.string(),
T: z.string(),
K: z.string(),
})),
initialDepths: z.array(z.object({ // 초기 차단 깊이
response: z.string(),
Dmax: z.string(),
Drec: z.string(),
Do: z.string(),
})),
// osumap: z.instanceof(File).optional(), // 오수관망도
// matching: z.instanceof(File).optional(), // 수용가-맨홀 매칭결과
aplcnYm: z.string(), // 상수사용량 적용기간 (YYYYMM 형식)
rate: z.string(), // 오수전환율
// rain: z.instanceof(File).optional(), // 강우자료
analysisInfo: z.string().optional(), // 분석 정보
date: z.date(),
// date: z.coerce.date(),
enoCd: z.string(), // 환경청코드
ctpvCd: z.string(), // 시도코드
sggCd: z.string(), // 시군구코드
swageTmplntCd: z.string(), // 하수처리장코드
swtrmtZoneCd: z.string(), // 하수처리구역코드
rtkSource: z.string(), // RTK계수 출처
rdiiArea: z.string(), // RDII 기여면적
unitFactors: z.array(z.object({ // 단위도 계수
response: z.string(),
R: z.string(),
T: z.string(),
K: z.string(),
})),
initialDepths: z.array(z.object({ // 초기 차단 깊이
response: z.string(),
Dmax: z.string(),
Drec: z.string(),
Do: z.string(),
})),
// osumap: z.instanceof(File).optional(), // 오수관망도
// matching: z.instanceof(File).optional(), // 수용가-맨홀 매칭결과
aplcnYm: z.string(), // 상수사용량 적용기간 (YYYYMM 형식)
rate: z.string(), // 오수전환율
// rain: z.instanceof(File).optional(), // 강우자료
analysisInfo: z.string().optional(), // 분석 정보
date: z.date(),
// date: z.coerce.date(),
})
// zod 스키마 타입 추론
type FormSchemaType = z.infer<typeof formSchema>;
// 기본 폼 값 정의 (테스트 편의를 위해 초기값 설정, 추후 삭제 필요)
const defaultFormValues: FormSchemaType = {
enoCd: "",
ctpvCd: "",
sggCd: "",
swageTmplntCd: "",
swtrmtZoneCd: "",
rtkSource: "출처",
rdiiArea: "123", // 문자열로 기본값 설정
unitFactors: [
{ response: "Short-Term", R: "0.040", T: "0.5", K: "1" }, // 문자열로 기본값 설정
{ response: "Medium-Term", R: "0.005", T: "1", K: "2" },
{ response: "Long-Term", R: "0.005", T: "2", K: "3" },
],
initialDepths: [
{ response: "Short-Term", Dmax: "0", Drec: "0", Do: "0" }, // 문자열로 기본값 설정
{ response: "Medium-Term", Dmax: "0", Drec: "0", Do: "0" },
{ response: "Long-Term", Dmax: "0", Drec: "0", Do: "0" },
],
// osumap: undefined,
// matchingResultFile: undefined,
aplcnYm: "",
rate: "0.8", // 문자열로 기본값 설정
// rainFile: undefined,
analysisInfo: "test 분석정보",
date: new Date(),
enoCd: "",
ctpvCd: "",
sggCd: "",
swageTmplntCd: "",
swtrmtZoneCd: "",
rtkSource: "출처",
rdiiArea: "123", // 문자열로 기본값 설정
unitFactors: [
{ response: "Short-Term", R: "0.040", T: "0.5", K: "1" }, // 문자열로 기본값 설정
{ response: "Medium-Term", R: "0.005", T: "1", K: "2" },
{ response: "Long-Term", R: "0.005", T: "2", K: "3" },
],
initialDepths: [
{ response: "Short-Term", Dmax: "0", Drec: "0", Do: "0" }, // 문자열로 기본값 설정
{ response: "Medium-Term", Dmax: "0", Drec: "0", Do: "0" },
{ response: "Long-Term", Dmax: "0", Drec: "0", Do: "0" },
],
// osumap: undefined,
// matchingResultFile: undefined,
aplcnYm: "",
rate: "0.8", // 문자열로 기본값 설정
// rainFile: undefined,
analysisInfo: "test 분석정보",
date: new Date(),
};
// 각 파일 업로드 폼의 데이터를 저장할 인터페이스 정의
interface UploadedFileInfo {
file: File | null;
atflCd: string | null;
atflId: string | null;
file: File | null;
atflCd: string | null;
atflId: string | null;
}
// 초기 파일 정보 상태
const initialFileInfo: UploadedFileInfo = { file: null, atflCd: null, atflId: null };
interface ApiSelectOption {
text: string;
val: string;
text: string;
val: string;
}
export default function RTKForm() {
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
// FileUploadForm에서 전달받은 파일 정보를 저장할 상태 배열
// 각 인덱스는 FileUploadForm의 fileIdx와 매칭됨
const [uploadedFilesData, setUploadedFilesData] = useState<UploadedFileInfo[]>([
initialFileInfo, // 0: 오수관망도
initialFileInfo, // 1: 수용가-맨홀 매칭결과
initialFileInfo, // 2: 강우자료
// FileUploadForm에서 전달받은 파일 정보를 저장할 상태 배열
// 각 인덱스는 FileUploadForm의 fileIdx와 매칭됨
const [uploadedFilesData, setUploadedFilesData] = useState<UploadedFileInfo[]>([
initialFileInfo, // 0: 오수관망도
initialFileInfo, // 1: 수용가-맨홀 매칭결과
initialFileInfo, // 2: 강우자료
]);
const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd)
const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd)
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 (sggCd)
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역 (swtrmtZoneCd)
// form 인스턴스 생성
const form = useForm<FormSchemaType>({
resolver: zodResolver(formSchema),
defaultValues: defaultFormValues
});
// 폼 초기화 함수
const resetAllForms = () => {
form.reset(defaultFormValues); // 폼 값 초기화
setUploadedFilesData([ // 파일 업로드 상태 초기화 (3개 파일)
initialFileInfo,
initialFileInfo,
initialFileInfo,
]);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
setSelect1('0');
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4('0');
setSelect5('0');
};
const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd)
const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd)
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 (sggCd)
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역 (swtrmtZoneCd)
// form 인스턴스 생성
const form = useForm<FormSchemaType>({
resolver: zodResolver(formSchema),
defaultValues: defaultFormValues
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
const handleFileSelection = (
index: number, // FileUploadForm의 fileIdx 값
selectedFile: File | null,
cd: string | null,
id: string | null
) => {
setUploadedFilesData(prevData => {
const newData = [...prevData];
newData[index] = {
file: selectedFile,
atflCd: cd,
atflId: id,
};
return newData;
});
console.log(`Page: File data received for form index ${index}`, { fileName: selectedFile?.name, cd, id });
};
// 폼 초기화 함수
const resetAllForms = () => {
form.reset(defaultFormValues); // 폼 값 초기화
setUploadedFilesData([ // 파일 업로드 상태 초기화 (3개 파일)
initialFileInfo,
initialFileInfo,
initialFileInfo,
]);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
setSelect1('0');
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4('0');
setSelect5('0');
};
const registFile = async (analysisData: any) => {
console.log("Submitting combined analysis data to backend");
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/rdiiAnls', analysisData);
if (data) {
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
} else alert("분석 데이터 제출 중 오류가 발생했습니다.");
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
const handleFileSelection = (
index: number, // FileUploadForm의 fileIdx 값
selectedFile: File | null,
cd: string | null,
id: string | null
) => {
setUploadedFilesData(prevData => {
const newData = [...prevData];
newData[index] = {
file: selectedFile,
atflCd: cd,
atflId: id,
};
return newData;
});
console.log(`Page: File data received for form index ${index}`, { fileName: selectedFile?.name, cd, id });
};
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
const formData = form.getValues();
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
const file3 = uploadedFilesData[2]; // 강우자료
const registFile = async (analysisData: any) => {
console.log("Submitting combined analysis data to backend");
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/rdiiAnls', analysisData);
if (data) {
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
} else alert("분석 데이터 제출 중 오류가 발생했습니다.");
};
console.log("오수관망도 정보:", file1?.file);
console.log("수용가-맨홀 매칭결과 정보:", file2);
console.log("강우자료:", file3);
// 침입수 분석 실행 버튼 클릭 핸들러
const handleAnalysisClick = async () => {
setIsLoadingResult(true); // 로딩 상태 시작
const formData = form.getValues();
console.log(uploadedFilesData);
const file1 = uploadedFilesData[0]; // 오수관망도
const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과
const file3 = uploadedFilesData[2]; // 강우자료
console.log("오수관망도 정보:", file1?.file);
console.log("수용가-맨홀 매칭결과 정보:", file2);
console.log("강우자료:", file3);
// 모든 필수 파일이 선택되었는지 확인
if (!file1?.file || !file2?.file || !file3?.file) {
setIsLoadingResult(false);
alert('모든 분석용 파일을 선택해주세요.');
return;
}
const filePickers = [
{ file: file2, parser: excelToMatchingResultData },
{ file: file3, parser: excelToRainData },
];
try {
const parsedResults = await Promise.all(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null; // fileInfo 객체 및 file 객체 null 체크
// filePicked 함수에 File 객체와 headerNum 전달
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
console.warn(`Warning: filePicked returned undefined for ${file.file.name}`);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null // 또는 적절한 기본값
};
}
// jsonData를 각 parser 함수로 처리
// parser 함수가 filePicked가 반환하는 JSON 형식을 직접 처리한다고 가정
const parsedDataBySpecificFunction = parser(jsonData);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: parsedDataBySpecificFunction
};
})
);
// 각 파싱 결과 접근
const matchingResultData = parsedResults.find(r => r && r.fileName === file2.file?.name)?.parsedData;
const rainData = parsedResults.find(r => r && r.fileName === file3.file?.name)?.parsedData;
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
aplcnYm: new Date(formData.date).toISOString().substring(0, 7).replace("-", ""), // 적용연월 (YYYYMM 형식)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name || "", // 오수관망도 파일명
atflMpngNm: file2?.file?.name || "", // 수용가-맨홀 매칭결과 파일명
atflRainNm: file3?.file?.name || "", // 강우자료 파일명
cntbArea: parseFloat(formData.rdiiArea) || null, // RDII 기여면적(ha)
srcNm: formData.rtkSource, // 출처이름 (RTK계수 출처)
shortPrdR: parseFloat(formData.unitFactors[0]?.R || "0"),
shortPrdT: parseFloat(formData.unitFactors[0]?.T || "0"),
shortPrdK: parseFloat(formData.unitFactors[0]?.K || "0"),
mdPrdR: parseFloat(formData.unitFactors[1]?.R || "0"),
mdPrdT: parseFloat(formData.unitFactors[1]?.T || "0"),
mdPrdK: parseFloat(formData.unitFactors[1]?.K || "0"),
longPrdR: parseFloat(formData.unitFactors[2]?.R || "0"),
longPrdT: parseFloat(formData.unitFactors[2]?.T || "0"),
longPrdK: parseFloat(formData.unitFactors[2]?.K || "0"),
shortPrdMaxDpth: parseFloat(formData.initialDepths[0]?.Dmax || "0"),
shortPrdRcvryRt: parseFloat(formData.initialDepths[0]?.Drec || "0"),
shortPrdBgngDpth: parseFloat(formData.initialDepths[0]?.Do || "0"),
mdPrdMaxDpth: parseFloat(formData.initialDepths[1]?.Dmax || "0"),
mdPrdRcvryRt: parseFloat(formData.initialDepths[1]?.Drec || "0"),
mdPrdBgngDpth: parseFloat(formData.initialDepths[1]?.Do || "0"),
longTermMaxDpth: parseFloat(formData.initialDepths[2]?.Dmax || "0"),
longTermRcvryRt: parseFloat(formData.initialDepths[2]?.Drec || "0"),
longTermBgngDpth: parseFloat(formData.initialDepths[2]?.Do || "0"),
drtwSwtcRt: parseFloat(formData.rate || "0"), // 오수전환율
anlsInfo: formData.analysisInfo, // 분석정보
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnRainData: rainData, // 강우자료
};
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
// 수정된 registFile 함수를 호출하여 combinedAnalysisData를 전송합니다.
await registFile(combinedAnalysisData);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("파일 파싱 중 오류 발생:", error);
alert("파일 처리 중 오류가 발생했습니다.");
return;
}
// 모든 필수 파일이 선택되었는지 확인
if (!file1?.file || !file2?.file || !file3?.file) {
setIsLoadingResult(false);
alert('모든 분석용 파일을 선택해주세요.');
return;
}
const { fields: unitFactorFields } = useFieldArray({
control: form.control,
name: "unitFactors"
})
const { fields: initialDepthFields } = useFieldArray({
control: form.control,
name: "initialDepths"
})
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log("✅ 제출 데이터:", data)
}
// 단위도 계수 테이블 설정 (EditableTableHeadItem 타입 사용 및 id 수정)
const unitFactorHeadlist: EditableTableHeadItem<FormSchemaType['unitFactors'][number]>[] = [
{ id: 'response', title: 'Response', editable: false, width: '150px' }, // width 속성 추가
{ id: 'R', title: 'R', editable: true, inputProps: { type: 'text' } },
{ id: 'T', title: 'T', editable: true, inputProps: { type: 'text' } },
{ id: 'K', title: 'K', editable: true, inputProps: { type: 'text' } }
const filePickers = [
{ file: file2, parser: excelToMatchingResultData },
{ file: file3, parser: excelToRainData },
];
// 초기 차단 깊이 테이블 설정 (EditableTableHeadItem 타입 사용)
const initialDepthHeadlist: EditableTableHeadItem<FormSchemaType['initialDepths'][number]>[] = [
{ id: 'response', title: 'Response', editable: false, width: '150px' }, // width 속성 추가
{ id: 'Dmax', title: 'Dmax', editable: true, inputProps: { type: 'text' } },
{ id: 'Drec', title: 'Drec', editable: true, inputProps: { type: 'text' } },
{ id: 'Do', title: 'Do', editable: true, inputProps: { type: 'text' } }
];
try {
const parsedResults = await Promise.all(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null; // fileInfo 객체 및 file 객체 null 체크
// 테이블 페이지 변경 핸들러
const handlePageChange = (page: number) => {
console.log('Page changed:', page)
// filePicked 함수에 File 객체와 headerNum 전달
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
console.warn(`Warning: filePicked returned undefined for ${file.file.name}`);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null // 또는 적절한 기본값
};
}
// jsonData를 각 parser 함수로 처리
// parser 함수가 filePicked가 반환하는 JSON 형식을 직접 처리한다고 가정
const parsedDataBySpecificFunction = parser(jsonData);
return {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: parsedDataBySpecificFunction
};
})
);
// 각 파싱 결과 접근
const matchingResultData = parsedResults.find(r => r && r.fileName === file2.file?.name)?.parsedData;
const rainData = parsedResults.find(r => r && r.fileName === file3.file?.name)?.parsedData;
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
aplcnYm: new Date(formData.date).toISOString().substring(0, 7).replace("-", ""), // 적용연월 (YYYYMM 형식)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name || "", // 오수관망도 파일명
atflMpngNm: file2?.file?.name || "", // 수용가-맨홀 매칭결과 파일명
atflRainNm: file3?.file?.name || "", // 강우자료 파일명
cntbArea: parseFloat(formData.rdiiArea) || null, // RDII 기여면적(ha)
srcNm: formData.rtkSource, // 출처이름 (RTK계수 출처)
shortPrdR: parseFloat(formData.unitFactors[0]?.R || "0"),
shortPrdT: parseFloat(formData.unitFactors[0]?.T || "0"),
shortPrdK: parseFloat(formData.unitFactors[0]?.K || "0"),
mdPrdR: parseFloat(formData.unitFactors[1]?.R || "0"),
mdPrdT: parseFloat(formData.unitFactors[1]?.T || "0"),
mdPrdK: parseFloat(formData.unitFactors[1]?.K || "0"),
longPrdR: parseFloat(formData.unitFactors[2]?.R || "0"),
longPrdT: parseFloat(formData.unitFactors[2]?.T || "0"),
longPrdK: parseFloat(formData.unitFactors[2]?.K || "0"),
shortPrdMaxDpth: parseFloat(formData.initialDepths[0]?.Dmax || "0"),
shortPrdRcvryRt: parseFloat(formData.initialDepths[0]?.Drec || "0"),
shortPrdBgngDpth: parseFloat(formData.initialDepths[0]?.Do || "0"),
mdPrdMaxDpth: parseFloat(formData.initialDepths[1]?.Dmax || "0"),
mdPrdRcvryRt: parseFloat(formData.initialDepths[1]?.Drec || "0"),
mdPrdBgngDpth: parseFloat(formData.initialDepths[1]?.Do || "0"),
longTermMaxDpth: parseFloat(formData.initialDepths[2]?.Dmax || "0"),
longTermRcvryRt: parseFloat(formData.initialDepths[2]?.Drec || "0"),
longTermBgngDpth: parseFloat(formData.initialDepths[2]?.Do || "0"),
drtwSwtcRt: parseFloat(formData.rate || "0"), // 오수전환율
anlsInfo: formData.analysisInfo, // 분석정보
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnRainData: rainData, // 강우자료
};
console.log("통합 분석 데이터 (JSON):", combinedAnalysisData);
// 수정된 registFile 함수를 호출하여 combinedAnalysisData를 전송합니다.
await registFile(combinedAnalysisData);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("파일 파싱 중 오류 발생:", error);
alert("파일 처리 중 오류가 발생했습니다.");
return;
}
}
return (
// <BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
<BaseLayout
selectBox={{ title: '분석 대상지역 선택' }}
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
form={form} // react-hook-form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={false}
analysisBtn={'RDII분석 실행'}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section">
<div className="grid grid-cols-2 gap-4">
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처">
<Input {...form.register("rtkSource")} className="h-12 text-center text-xl w-[225px]" />
</FormSection>
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)">
<Input {...form.register("rdiiArea")} className="h-12 text-center text-xl w-[225px]" />
</FormSection></div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="mb-2 text-xl font-medium">○ 단위도 계수</Label>
<EditableFormTable
form={form}
name="unitFactors"
fields={unitFactorFields}
headlist={unitFactorHeadlist}
tableClassName="w-[400px]"
/>
</div>
<div>
<Label className="mb-[1.2rem] text-xl font-medium">○ 초기 차단 깊이</Label>
<EditableFormTable
form={form}
name="initialDepths"
fields={initialDepthFields}
headlist={initialDepthHeadlist}
tableClassName="w-[400px]"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
showButton={false}
showPreview={true}
accept={""}
fileIdx={0} />
</div></FormSection>
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={1} />
</div></FormSection>
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</div></FormSection>
<FormSection name='rate' form={form} titleNm='○ 오수전환율'>
<Input {...form.register("rate")} className="h-12 text-center text-xl w-[225px] ml-5" />
</FormSection>
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용기간'>
{/* <DatePicker
const { fields: unitFactorFields } = useFieldArray({
control: form.control,
name: "unitFactors"
})
const { fields: initialDepthFields } = useFieldArray({
control: form.control,
name: "initialDepths"
})
const onSubmit = (data: z.infer<typeof formSchema>) => {
console.log("✅ 제출 데이터:", data)
}
// 단위도 계수 테이블 설정 (EditableTableHeadItem 타입 사용 및 id 수정)
const unitFactorHeadlist: EditableTableHeadItem<FormSchemaType['unitFactors'][number]>[] = [
{ id: 'response', title: 'Response', editable: false, width: '150px' }, // width 속성 추가
{ id: 'R', title: 'R', editable: true, inputProps: { type: 'text' } },
{ id: 'T', title: 'T', editable: true, inputProps: { type: 'text' } },
{ id: 'K', title: 'K', editable: true, inputProps: { type: 'text' } }
];
// 초기 차단 깊이 테이블 설정 (EditableTableHeadItem 타입 사용)
const initialDepthHeadlist: EditableTableHeadItem<FormSchemaType['initialDepths'][number]>[] = [
{ id: 'response', title: 'Response', editable: false, width: '150px' }, // width 속성 추가
{ id: 'Dmax', title: 'Dmax', editable: true, inputProps: { type: 'text' } },
{ id: 'Drec', title: 'Drec', editable: true, inputProps: { type: 'text' } },
{ id: 'Do', title: 'Do', editable: true, inputProps: { type: 'text' } }
];
// 테이블 페이지 변경 핸들러
const handlePageChange = (page: number) => {
console.log('Page changed:', page)
}
return (
// <BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
<BaseLayout
selectBox={{ title: '분석 대상지역 선택' }}
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
form={form} // react-hook-form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={false}
analysisBtn={'RDII분석 실행'}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section">
<div className="grid grid-cols-2 gap-4">
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처">
<Input {...form.register("rtkSource")} className="h-12 text-center text-xl w-[225px]" />
</FormSection>
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)">
<Input {...form.register("rdiiArea")} className="h-12 text-center text-xl w-[225px]" />
</FormSection></div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="mb-2 text-xl font-medium">○ 단위도 계수</Label>
<EditableFormTable
form={form}
name="unitFactors"
fields={unitFactorFields}
headlist={unitFactorHeadlist}
tableClassName="w-[400px]"
/>
</div>
<div>
<Label className="mb-[1.2rem] text-xl font-medium">○ 초기 차단 깊이</Label>
<EditableFormTable
form={form}
name="initialDepths"
fields={initialDepthFields}
headlist={initialDepthHeadlist}
tableClassName="w-[400px]"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
showButton={false}
showPreview={true}
accept={""}
fileIdx={0} />
</div></FormSection>
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={1} />
</div></FormSection>
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[350px]">
<FileUploadForm
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
showButton={false}
showPreview={true}
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</div></FormSection>
<FormSection name='rate' form={form} titleNm='○ 오수전환율'>
<Input {...form.register("rate")} className="h-12 text-center text-xl w-[225px] ml-5" />
</FormSection>
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용기간'>
{/* <DatePicker
selected={form.watch("date")}
onSelect={(date: Date | undefined) => {
if (date) {
@@ -413,30 +413,30 @@ export default function RTKForm() {
mode={"single"}
className="ml-5"
/> */}
<DatePicker
mode={"single"} // mode는 DayPickerSingleProps에서 오므로 그대로 사용 가능
field={{
value: form.watch("date"), // react-hook-form의 "date" 필드 값 사용
onChange: (date: Date | undefined) => {
if (date) {
form.setValue("date", date, { shouldValidate: true });
}
}
}}
// className을 직접 사용하거나, DatePicker 내부에서 selectClass를 처리하도록 수정 필요
// 여기서는 DatePicker의 selectClass prop을 사용하고, date-picker.css의 스타일을 적용
selectClass="ml-5 date-picker-trigger"
/>
</FormSection>
<FormSection name='rate' form={form} titleNm='○ 분석 정보'>
<Input {...form.register("analysisInfo")} className="h-12 text-center text-xl w-[225px] ml-5" />
</FormSection>
</div>
{/* <div className="flex justify-end mt-1">
<DatePicker
mode={"single"} // mode는 DayPickerSingleProps에서 오므로 그대로 사용 가능
field={{
value: form.watch("date"), // react-hook-form의 "date" 필드 값 사용
onChange: (date: Date | undefined) => {
if (date) {
form.setValue("date", date, { shouldValidate: true });
}
}
}}
// className을 직접 사용하거나, DatePicker 내부에서 selectClass를 처리하도록 수정 필요
// 여기서는 DatePicker의 selectClass prop을 사용하고, date-picker.css의 스타일을 적용
selectClass="ml-5 date-picker-trigger"
/>
</FormSection>
<FormSection name='rate' form={form} titleNm='○ 분석 정보'>
<Input {...form.register("analysisInfo")} className="h-12 text-center text-xl w-[225px] ml-5" />
</FormSection>
</div>
{/* <div className="flex justify-end mt-1">
<Button size="sm" type="submit" onClick={handleAnalysisClick}>RDII분석 실행</Button>
</div> */}
</div>
]}
</BaseLayout >
)
</div>
]}
</BaseLayout >
)
}
@@ -10,259 +10,259 @@ import { handleDelete } from '@/components/lib/utils'
const formSchema = z.object({
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동)
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동)
})
type ResultFormValues = z.infer<typeof formSchema>
const tableColumn = [
{ id: 'rainInfwtAnlsSn', title: '분석번호' },
{ id: 'swageTmplntNm', title: '하수처리장' },
// { id: 'swtrmtZoneNm', title: '처리구역' },
{ id: 'aplcnYm', title: '상수사용연월' },
{ id: 'drtwSwtcRt', title: '오수 전환율' },
{ id: 'srcNm', title: 'RTX 계수 출처' },
{ id: 'cntbArea', title: 'RDII 기여면적' },
{ id: 'anlsInfo', title: 'RDII 분석정보' },
{ id: 'regDt', title: '분석일자' },
{ id: 'rgtrNm', title: '분석자' }
{ id: 'rainInfwtAnlsSn', title: '분석번호' },
{ id: 'swageTmplntNm', title: '하수처리장' },
// { id: 'swtrmtZoneNm', title: '처리구역' },
{ id: 'aplcnYm', title: '상수사용연월' },
{ id: 'drtwSwtcRt', title: '오수 전환율' },
{ id: 'srcNm', title: 'RTX 계수 출처' },
{ id: 'cntbArea', title: 'RDII 기여면적' },
{ id: 'anlsInfo', title: 'RDII 분석정보' },
{ id: 'regDt', title: '분석일자' },
{ id: 'rgtrNm', title: '분석자' }
]
const Result = () => {
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYm: new Date(),
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYm: new Date(),
}
})
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
setIsLoadingResult(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
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',
size: '10000000',
sort: 'rainInfwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
if (value !== undefined && value !== null && String(value).trim() !== '') { // undefined, null, 빈 문자열이 아닌 경우에만 추가
queryParams.append(key, String(value));
}
});
const queryString = queryParams.toString();
console.log("API: ", `v1/rdiiAnls${queryString ? `?${queryString}` : ''}`);
try {
const data = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`);
setTableData(data.content || []);
setTotalData(tableData.length);
console.log("response:", data);
console.log("response data:", data.content || []);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Error fetching analysis result data:", error);
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
}
};
// 테이블 셀 클릭 핸들러 추가
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 () => {
setIsLoadingResult(true);
console.log("Selected row data for download:", selectedRowData);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('다운로드할 데이터를 선택해주세요.');
return;
}
try {
const data = await getData(`v1/rdiiAnlsRslt/ppl/${selectedRowData.rainInfwtAnlsSn}`);
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = data.content; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const fileName = `RDII분석결과_${selectedRowData.rainInfwtAnlsSn}_${selectedRowData.aplcnYm}`;
downloadExcel({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: ["RDII 분석결과"],
headerRange: "A1:D1" // 테이블 컬럼 수에 맞춰 조정
});
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
// 파일 삭제 핸들러 추가
const handleFileDelete = async () => {
setIsLoadingResult(true);
console.log("Selected row data for deletion:", selectedRowData);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
alert('삭제할 파일을 선택해주세요.');
return;
}
// 삭제 확인 대화상자
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
if (!confirmDelete) {
return;
}
try {
// 삭제 API 호출
const response1 = await deleteData(`v1/rdiiAnlsRslt/${selectedRowData.rainInfwtAnlsSn}`);
const response2 = await deleteData(`v1/rdiiAnls/${selectedRowData.rainInfwtAnlsSn}`);
// 첨부파일(inp파일) 삭제 API 호출
handleDelete(selectedRowData.atflCd, {
handleDeleteStart: () => console.log("파일 삭제 시작"),
handleDeleteSuccess: (response) => console.log("파일 삭제 성공:", response),
handleDeleteError: (error) => console.error("파일 삭제 실패:", error),
});
console.log("File delete response1:", response1);
console.log("File delete response2:", response2);
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYm: selectedRowData.aplcnYm || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null,
};
await handleSearchButtonClick(customFormValues);
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setIsLoadingResult(false);
alert('삭제가 완료되었습니다.');
setTimeout(() => {
setResetSelection(false);
}, 100);
} catch (error) {
console.error("분석결과 파일 삭제 중 오류 발생:", error);
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
}
setIsLoadingResult(false);
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
titleName={{ mainTitle: '분석결과', firstCardTitle: 'RDII 산정결과 현황' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
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))
}
})
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
setIsLoadingResult(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
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',
size: '10000000',
sort: 'rainInfwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
if (value !== undefined && value !== null && String(value).trim() !== '') { // undefined, null, 빈 문자열이 아닌 경우에만 추가
queryParams.append(key, String(value));
}
});
const queryString = queryParams.toString();
console.log("API: ", `v1/rdiiAnls${queryString ? `?${queryString}` : ''}`);
try {
const data = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`);
setTableData(data.content || []);
setTotalData(tableData.length);
console.log("response:", data);
console.log("response data:", data.content || []);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Error fetching analysis result data:", error);
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
}
};
// 테이블 셀 클릭 핸들러 추가
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 () => {
setIsLoadingResult(true);
console.log("Selected row data for download:", selectedRowData);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('다운로드할 데이터를 선택해주세요.');
return;
}
try {
const data = await getData(`v1/rdiiAnlsRslt/ppl/${selectedRowData.rainInfwtAnlsSn}`);
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = data.content; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const fileName = `RDII분석결과_${selectedRowData.rainInfwtAnlsSn}_${selectedRowData.aplcnYm}`;
downloadExcel({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: ["RDII 분석결과"],
headerRange: "A1:D1" // 테이블 컬럼 수에 맞춰 조정
});
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
// 파일 삭제 핸들러 추가
const handleFileDelete = async () => {
setIsLoadingResult(true);
console.log("Selected row data for deletion:", selectedRowData);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
alert('삭제할 파일을 선택해주세요.');
return;
}
// 삭제 확인 대화상자
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
if (!confirmDelete) {
return;
}
try {
// 삭제 API 호출
const response1 = await deleteData(`v1/rdiiAnlsRslt/${selectedRowData.rainInfwtAnlsSn}`);
const response2 = await deleteData(`v1/rdiiAnls/${selectedRowData.rainInfwtAnlsSn}`);
// 첨부파일(inp파일) 삭제 API 호출
handleDelete(selectedRowData.atflCd, {
handleDeleteStart: () => console.log("파일 삭제 시작"),
handleDeleteSuccess: (response) => console.log("파일 삭제 성공:", response),
handleDeleteError: (error) => console.error("파일 삭제 실패:", error),
});
console.log("File delete response1:", response1);
console.log("File delete response2:", response2);
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYm: selectedRowData.aplcnYm || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null,
};
await handleSearchButtonClick(customFormValues);
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setIsLoadingResult(false);
alert('삭제가 완료되었습니다.');
setTimeout(() => {
setResetSelection(false);
}, 100);
} catch (error) {
console.error("분석결과 파일 삭제 중 오류 발생:", error);
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
}
setIsLoadingResult(false);
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
titleName={{ mainTitle: '분석결과', firstCardTitle: 'RDII 산정결과 현황' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
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))
}
}}
isLoading={isLoadingResult}
// 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">○ RDII 분석결과 현황</div>
<div className="flex-grow overflow-auto">
<UiTable
headName=''
headlist={tableColumn}
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
resetSelection={resetSelection}
/>
</div>
</div>
]}
</BaseLayout>
)
}}
isLoading={isLoadingResult}
// 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">○ RDII 분석결과 현황</div>
<div className="flex-grow overflow-auto">
<UiTable
headName=''
headlist={tableColumn}
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
resetSelection={resetSelection}
/>
</div>
</div>
]}
</BaseLayout>
)
}
export default Result
+245 -245
View File
@@ -10,260 +10,260 @@ import { handleDelete } from '@/components/lib/utils'
const formSchema = z.object({
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동)
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYm: z.date().default(new Date()), // 분석일자 (DatePicker와 연동)
})
type ResultFormValues = z.infer<typeof formSchema>
const tableColumn = [
// { id: 'no', title: '번호' },
{ id: 'inftwtAnlsSn', title: '분석번호' },
{ id: 'swageTmplntNm', title: '하수처리장' },
// { id: 'swtrmtZoneNm', title: '처리구역' },
{ id: 'aplcnYm', title: '적용연월' },
{ id: 'drtwSwtcRt', title: '오수 전환율' },
{ id: 'anlsInfo', title: '침입수 분석정보' },
{ id: 'regDt', title: '분석일자' },
{ id: 'rgtrNm', title: '분석자' }
// { id: 'no', title: '번호' },
{ id: 'inftwtAnlsSn', title: '분석번호' },
{ id: 'swageTmplntNm', title: '하수처리장' },
// { id: 'swtrmtZoneNm', title: '처리구역' },
{ id: 'aplcnYm', title: '적용연월' },
{ id: 'drtwSwtcRt', title: '오수 전환율' },
{ id: 'anlsInfo', title: '침입수 분석정보' },
{ id: 'regDt', title: '분석일자' },
{ id: 'rgtrNm', title: '분석자' }
]
const Result = () => {
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYm: new Date(),
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYm: new Date(),
}
})
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
setIsLoadingResult(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
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',
size: '10000000',
sort: 'inftwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
if (value !== undefined && value !== null && String(value).trim() !== '') { // undefined, null, 빈 문자열이 아닌 경우에만 추가
queryParams.append(key, String(value));
}
});
const queryString = queryParams.toString();
console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
try {
const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
setTableData(data.content || []);
setTotalData(tableData.length);
console.log("response:", data);
console.log("response data:", data.content || []);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Error fetching analysis result data:", error);
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
}
};
// 테이블 셀 클릭 핸들러 추가
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 () => {
setIsLoadingResult(true);
console.log("Selected row data for download:", selectedRowData);
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('다운로드할 데이터를 선택해주세요.');
return;
}
try {
const data = await getData(`v1/inftwtAnlsRslt/ppl/${selectedRowData.inftwtAnlsSn}`);
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = data.content; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const fileName = `침입수분석결과_${selectedRowData.inftwtAnlsSn}_${selectedRowData.aplcnYm}`;
downloadExcel({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: ["침입수 분석결과"],
headerRange: "A1:D1" // 테이블 컬럼 수에 맞춰 조정
});
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
// 파일 삭제 핸들러 추가
const handleFileDelete = async () => {
setIsLoadingResult(true);
console.log("Selected row data for deletion:", selectedRowData);
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('삭제할 파일을 선택해주세요.');
return;
}
// 삭제 확인 대화상자
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
if (!confirmDelete) {
return;
}
try {
// // 삭제 API 호출
console.log("Deleting analysis result with inftwtAnlsSn:", selectedRowData.inftwtAnlsSn);
const response1 = await deleteData(`v1/inftwtAnlsRslt/${selectedRowData.inftwtAnlsSn}`);
const response2 = await deleteData(`v1/inftwtAnls/${selectedRowData.inftwtAnlsSn}`);
// 첨부파일(inp파일) 삭제 API 호출
handleDelete(selectedRowData.atflCd, {
handleDeleteStart: () => console.log("파일 삭제 시작"),
handleDeleteSuccess: (response) => console.log("파일 삭제 성공:", response),
handleDeleteError: (error) => console.error("파일 삭제 실패:", error),
});
console.log("File delete response1:", response1);
console.log("File delete response2:", response2);
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYm: selectedRowData.aplcnYm || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null,
};
await handleSearchButtonClick(customFormValues);
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setIsLoadingResult(false);
alert('삭제가 완료되었습니다.');
setTimeout(() => {
setResetSelection(false);
}, 100);
} catch (error) {
setIsLoadingResult(false);
console.error("분석결과 파일 삭제 중 오류 발생:", error);
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
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))
}
})
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
setIsLoadingResult(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
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',
size: '10000000',
sort: 'inftwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
if (value !== undefined && value !== null && String(value).trim() !== '') { // undefined, null, 빈 문자열이 아닌 경우에만 추가
queryParams.append(key, String(value));
}
});
const queryString = queryParams.toString();
console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
try {
const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
setTableData(data.content || []);
setTotalData(tableData.length);
console.log("response:", data);
console.log("response data:", data.content || []);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Error fetching analysis result data:", error);
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
}
};
// 테이블 셀 클릭 핸들러 추가
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 () => {
setIsLoadingResult(true);
console.log("Selected row data for download:", selectedRowData);
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('다운로드할 데이터를 선택해주세요.');
return;
}
try {
const data = await getData(`v1/inftwtAnlsRslt/ppl/${selectedRowData.inftwtAnlsSn}`);
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = data.content; // 배열로 감싸서 전달
// 파일명 생성 (분석번호 기반)
const fileName = `침입수분석결과_${selectedRowData.inftwtAnlsSn}_${selectedRowData.aplcnYm}`;
downloadExcel({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: ["침입수 분석결과"],
headerRange: "A1:D1" // 테이블 컬럼 수에 맞춰 조정
});
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
// 파일 삭제 핸들러 추가
const handleFileDelete = async () => {
setIsLoadingResult(true);
console.log("Selected row data for deletion:", selectedRowData);
if (!selectedRowData || selectedRowData.inftwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('삭제할 파일을 선택해주세요.');
return;
}
// 삭제 확인 대화상자
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
if (!confirmDelete) {
return;
}
try {
// // 삭제 API 호출
console.log("Deleting analysis result with inftwtAnlsSn:", selectedRowData.inftwtAnlsSn);
const response1 = await deleteData(`v1/inftwtAnlsRslt/${selectedRowData.inftwtAnlsSn}`);
const response2 = await deleteData(`v1/inftwtAnls/${selectedRowData.inftwtAnlsSn}`);
// 첨부파일(inp파일) 삭제 API 호출
handleDelete(selectedRowData.atflCd, {
handleDeleteStart: () => console.log("파일 삭제 시작"),
handleDeleteSuccess: (response) => console.log("파일 삭제 성공:", response),
handleDeleteError: (error) => console.error("파일 삭제 실패:", error),
});
console.log("File delete response1:", response1);
console.log("File delete response2:", response2);
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYm: selectedRowData.aplcnYm || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null,
};
await handleSearchButtonClick(customFormValues);
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setIsLoadingResult(false);
alert('삭제가 완료되었습니다.');
setTimeout(() => {
setResetSelection(false);
}, 100);
} catch (error) {
setIsLoadingResult(false);
console.error("분석결과 파일 삭제 중 오류 발생:", error);
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
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))
}
}}
isLoading={isLoadingResult} // 로딩 상태 전달
// 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>
<div className="flex-grow overflow-auto">
<UiTable
headName=''
headlist={tableColumn}
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
resetSelection={resetSelection}
/>
</div>
</div>
]}
</BaseLayout>
)
}}
isLoading={isLoadingResult} // 로딩 상태 전달
// 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>
<div className="flex-grow overflow-auto">
<UiTable
headName=''
headlist={tableColumn}
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
resetSelection={resetSelection}
/>
</div>
</div>
]}
</BaseLayout>
)
}
export default Result
@@ -12,150 +12,150 @@ import { deleteData, postData } from '@/components/lib/api'
import LoadingOverlay from '@/components/ui/LoadingBar'
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('') //파일명
const [atflId, setAtflId] = useState('') //파일 ID
const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') //파일 일련번호
const [tableData, setTableData] = useState<any>([{}]) //테이블 데이터
const [curPage, setCurPage] = useState(1) //현재 페이지
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoading, setIsLoading] = useState(true) //로딩 상태
const fetchData = async () => {
setIsLoading(true)
searchCmmnCd('SITEKNDCD')
try {
const { data, total } = await searchData('v1/sbmrgPrevnt', {
fromRegDt: formatDate(fromDate),
fromMngYr: manageYear,
toMngYr: mngQu,
toRegDt: formatDate(toDate),
atflNm: pjNm,
rgtrNm: usr,
page: curPage - 1,
size: '10'
})
setTableData(data)
setTotalData(total)
}
catch (error) { console.error('Error fetching data:', error) }
finally { setIsLoading(false); }
// 조회조건
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('') //파일명
const [atflId, setAtflId] = useState('') //파일 ID
const [sbmrgPrevntBizSn, setSbmrgPrevntBizSn] = useState('') //파일 일련번호
const [tableData, setTableData] = useState<any>([{}]) //테이블 데이터
const [curPage, setCurPage] = useState(1) //현재 페이지
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoading, setIsLoading] = useState(true) //로딩 상태
const fetchData = async () => {
setIsLoading(true)
searchCmmnCd('SITEKNDCD')
try {
const { data, total } = await searchData('v1/sbmrgPrevnt', {
fromRegDt: formatDate(fromDate),
fromMngYr: manageYear,
toMngYr: mngQu,
toRegDt: formatDate(toDate),
atflNm: pjNm,
rgtrNm: usr,
page: curPage - 1,
size: '10'
})
setTableData(data)
setTotalData(total)
}
useEffect(() => {
setCurPage(1)
catch (error) { console.error('Error fetching data:', error) }
finally { setIsLoading(false); }
}
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
alert('파일을 선택해주세요')
return false
}
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm)
}
// 선택 파일 삭제
const isDeleteFile = async () => {
if (sbmrgPrevntBizSn === '' || sbmrgPrevntBizSn === undefined) {
alert('삭제할 파일을 선택해주세요')
return false
}
const data = await deleteData(`/attach-service/api/v1/atfl/attachments/${atflId}`)
if (data) {
const delData = await deleteData(`v1/sbmrgPrevnt/${sbmrgPrevntBizSn}`)
if (delData.status === 200) {
alert('삭제되었습니다.')
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
alert('파일을 선택해주세요')
return false
}
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm)
}
}
// 선택 파일 삭제
const isDeleteFile = async () => {
if (sbmrgPrevntBizSn === '' || sbmrgPrevntBizSn === undefined) {
alert('삭제할 파일을 선택해주세요')
return false
}
const data = await deleteData(`/attach-service/api/v1/atfl/attachments/${atflId}`)
if (data) {
const delData = await deleteData(`v1/sbmrgPrevnt/${sbmrgPrevntBizSn}`)
if (delData.status === 200) {
alert('삭제되었습니다.')
fetchData()
}
}
}
// 현황 등록
const registFile = async (fileName: string, year: string, quater: string, cd: string, id: string) => {
const data = await postData('v1/sbmrgPrevnt', {
atflNm: fileName,
mngYr: year,
mngQu: quater,
atflCd: cd,
atflId: id,
sbmrgPrevntBizNm: fileName
})
if (data) { alert('파일이 등록되었습니다.'); fetchData() }
}
return (
<ContentLayout title='도시침수 예방사업' isLoading={isLoading}>
<SearchDiv>
<SearchFormLeft>
<div className='flex items-center gap-5'>
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
<DatePicker mode={'single'}
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
</div>
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">관리년도</Label>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setManageYear(String(value))}
/>
<span>~</span>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setMngQu(String(value))}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">등록자</Label>
<Input
className='w-[100px]'
value={usr}
onChange={(e) => setUsr(e.target.value)}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">파일명</Label>
<Input
className='w-[100px]'
value={pjNm}
onChange={(e) => setpjNm(e.target.value)}
/>
</div>
</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>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
<div>
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile} >파일 다운로드</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile} >삭제</Button>
</div>
</div>
<UiTable headName='' tableData={tableData} headlist={tableHeadlist} pageSize={10} total={totalData} cellClick={(index) => { if (index != null) { const fileName = tableData[index]?.sbmrgPrevntBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn); } }} onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}></UiTable>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='도시침수 예방사업 사업추진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
</ContentLayout>
)
}
// 현황 등록
const registFile = async (fileName: string, year: string, quater: string, cd: string, id: string) => {
const data = await postData('v1/sbmrgPrevnt', {
atflNm: fileName,
mngYr: year,
mngQu: quater,
atflCd: cd,
atflId: id,
sbmrgPrevntBizNm: fileName
})
if (data) { alert('파일이 등록되었습니다.'); fetchData() }
}
return (
<ContentLayout title='도시침수 예방사업' isLoading={isLoading}>
<SearchDiv>
<SearchFormLeft>
<div className='flex items-center gap-5'>
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
<DatePicker mode={'single'}
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
</div>
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">관리년도</Label>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setManageYear(String(value))}
/>
<span>~</span>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setMngQu(String(value))}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">등록자</Label>
<Input
className='w-[100px]'
value={usr}
onChange={(e) => setUsr(e.target.value)}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">파일명</Label>
<Input
className='w-[100px]'
value={pjNm}
onChange={(e) => setpjNm(e.target.value)}
/>
</div>
</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>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
<div>
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile} >파일 다운로드</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile} >삭제</Button>
</div>
</div>
<UiTable headName='' tableData={tableData} headlist={tableHeadlist} pageSize={10} total={totalData} cellClick={(index) => { if (index != null) { const fileName = tableData[index]?.sbmrgPrevntBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn); } }} onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}></UiTable>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='도시침수 예방사업 사업추진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
</ContentLayout>
)
}
export default Page
+12 -12
View File
@@ -1,17 +1,17 @@
export const tableHeadlist = [
{ id: 'sbmrgPrevntBizSn', title: '일련번호' },
{ id: 'sbmrgPrevntBizNm', title: '파일명' },
{ id: 'mngYr', title: '년도' },
{ id: 'mngQu', title: '분기' },
{ id: 'regDt', title: '등록 일자' },
{ id: 'rgtr', title: '등록자' }
{ id: 'sbmrgPrevntBizSn', title: '일련번호' },
{ id: 'sbmrgPrevntBizNm', title: '파일명' },
{ id: 'mngYr', title: '년도' },
{ id: 'mngQu', title: '분기' },
{ id: 'regDt', title: '등록 일자' },
{ id: 'rgtr', title: '등록자' }
]
export const tableHeadlistOldPp = [
{ id: 'dcrpPplMtncBizSn', title: '일련번호' },
{ id: 'dcrpPplMtncBizNm', title: '파일명' },
{ id: 'mngYr', title: '년도' },
{ id: 'mngQu', title: '분기' },
{ id: 'regDt', title: '등록 일자' },
{ id: 'rgtr', title: '등록자' }
{ id: 'dcrpPplMtncBizSn', title: '일련번호' },
{ id: 'dcrpPplMtncBizNm', title: '파일명' },
{ id: 'mngYr', title: '년도' },
{ id: 'mngQu', title: '분기' },
{ id: 'regDt', title: '등록 일자' },
{ id: 'rgtr', title: '등록자' }
]
+143 -142
View File
@@ -10,151 +10,152 @@ import { searchData } from '@/components/lib/common/apiHelpers'
import { formatDate, yearOptions, handleDownload } from '@/components/lib/utils'
import { deleteData, postData } from '@/components/lib/api'
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('')
const [atflId, setAtflId] = useState('')
const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('')
const [tableData, setTableData] = useState<any>([{}])
const [curPage, setCurPage] = useState(1)
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoading, setIsLoading] = useState(true) //로딩 상태
// 데이터 조회
const fetchData = async () => {
setIsLoading(true)
try {
const {data, total} = await searchData('v1/dcrpPpl', {
fromRegDt: formatDate(fromDate),
fromMngYr: manageYear,
toMngYr: mngQu,
toRegDt: formatDate(toDate),
dcrpPplMtncBizNm: pjNm,
rgtrNm: usr,
page: curPage - 1,
size: '10'
})
setTableData(data)
setTotalData(total)
console.log(data, total)
}catch (error) {
console.error('Error fetching data:', error)}finally{setIsLoading(false)}
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('')
const [atflId, setAtflId] = useState('')
const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('')
const [tableData, setTableData] = useState<any>([{}])
const [curPage, setCurPage] = useState(1)
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoading, setIsLoading] = useState(true) //로딩 상태
// 데이터 조회
const fetchData = async () => {
setIsLoading(true)
try {
const { data, total } = await searchData('v1/dcrpPpl', {
fromRegDt: formatDate(fromDate),
fromMngYr: manageYear,
toMngYr: mngQu,
toRegDt: formatDate(toDate),
dcrpPplMtncBizNm: pjNm,
rgtrNm: usr,
page: curPage - 1,
size: '10'
})
setTableData(data)
setTotalData(total)
console.log(data, total)
} catch (error) {
console.error('Error fetching data:', error)
} finally { setIsLoading(false) }
}
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
alert('파일을 선택해주세요')
return false
}
useEffect(() => {
setCurPage(1)
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('삭제되었습니다.')
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
alert('파일을 선택해주세요')
return false
}
handleDownload(`/attach-service/api/v1/download/${atflId}`, fileNm)
return true
} else {
alert('삭제에 실패했습니다.')
}
}
// 파일 삭제
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('삭제되었습니다.')
fetchData()
} else {
alert('삭제에 실패했습니다.')
}
}
}
// 파일 등록
const registFile = async (fileNm: string, year: string, quater: string, cd: string, id: string) => {
const data = await postData('v1/dcrpPpl', {
atflNm: fileNm,
mngYr: year,
mngQu: quater,
atflCd: cd,
atflId: id,
dcrpPplMtncBizNm: fileNm
})
if (data) {alert('파일이 등록되었습니다.');fetchData() }
}
return (
<>
<SearchDiv>
<SearchFormLeft>
<div className='flex items-center gap-4'>
<div className='flex items-center space-x-2'>
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
<DatePicker mode={'single'}
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">관리년도</Label>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setManageYear(String(value))}
/>
<span>~</span>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setMngQu(String(value))}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">등록자</Label>
<Input
className='w-[100px] !text-2xl'
value={usr}
onChange={(e) => setUsr(e.target.value)}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">파일명</Label>
<Input
className='w-[100px] !text-2xl'
value={pjNm}
onChange={(e) => {setpjNm(e.target.value)}}
/>
</div>
</div>
</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>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
<div>
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile}>파일 다운로드</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile}>삭제</Button>
</div>
</div>
<UiTable headName='' tableData={tableData} headlist={tableHeadlistOldPp} pageSize={10} total={totalData} children={undefined} cellClick={(index) => { if (index != null) { const fileName = tableData[index]?.dcrpPplMtncBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn) } }} onPageChange={async (page:number)=>{setCurPage(page); await fetchData()}}></UiTable>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='노후 하수관로 정비사업 주진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
</>
)
}
// 파일 등록
const registFile = async (fileNm: string, year: string, quater: string, cd: string, id: string) => {
const data = await postData('v1/dcrpPpl', {
atflNm: fileNm,
mngYr: year,
mngQu: quater,
atflCd: cd,
atflId: id,
dcrpPplMtncBizNm: fileNm
})
if (data) { alert('파일이 등록되었습니다.'); fetchData() }
}
return (
<>
<SearchDiv>
<SearchFormLeft>
<div className='flex items-center gap-4'>
<div className='flex items-center space-x-2'>
<Label className="w-20 text-xl text-gray-600">등록기간</Label>
<DatePicker mode={'single'}
selected={fromDate}
onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} />
<span>~</span>
<DatePicker mode={'single'}
selected={toDate}
onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} />
<div className='flex items-center gap-2'>
<Label className="w-20 text-xl text-gray-600">관리년도</Label>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setManageYear(String(value))}
/>
<span>~</span>
<SelectBox
className="w-[90px] mr-3 ml-4"
selectClass="rounded-lg bg-white"
label=""
selectArray={yearOptions}
onSelectValue={(value: string | number) => setMngQu(String(value))}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">등록자</Label>
<Input
className='w-[100px] !text-2xl'
value={usr}
onChange={(e) => setUsr(e.target.value)}
/>
</div>
<div className='flex items-center'>
<Label className="w-20 text-xl text-gray-600">파일명</Label>
<Input
className='w-[100px] !text-2xl'
value={pjNm}
onChange={(e) => { setpjNm(e.target.value) }}
/>
</div>
</div>
</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>
</SearchFormRight>
</SearchDiv>
<ListDiv className="mt-4">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-medium">사업정보 등록현황</h3>
<div>
<Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile}>파일 다운로드</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile}>삭제</Button>
</div>
</div>
<UiTable headName='' tableData={tableData} headlist={tableHeadlistOldPp} pageSize={10} total={totalData} children={undefined} cellClick={(index) => { if (index != null) { const fileName = tableData[index]?.dcrpPplMtncBizNm ?? ""; setFileNm(fileName); const id = tableData[index]?.atflId ?? ''; setAtflId(id); setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn) } }} onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}></UiTable>
</ListDiv>
<ListDiv className="mt-4">
<OperFileUploadForm title='노후 하수관로 정비사업 주진현황 등록' onSubmit={async (file, year, quater, cd, id) => { await registFile(file, year, quater, cd, id) }} />
</ListDiv>
</>
)
}
export default Page
+52 -52
View File
@@ -1,68 +1,68 @@
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || '';
export const postData = async (url: string, data: object): Promise<any> => {
const options: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
};
const res = await fetch(`${BASE_URL}/${url}`, options);
if (res.ok) {
const resData = await res.json();
return resData;
// const text = await res.text();
// if (text) {
// return JSON.parse(text); // JSON 데이터가 있으면 파싱하여 반환
// }
// return true; // 응답 본문이 비어 있으면 true 반환
}
const options: RequestInit = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
};
const res = await fetch(`${BASE_URL}/${url}`, options);
if (res.ok) {
const resData = await res.json();
return resData;
// const text = await res.text();
// if (text) {
// return JSON.parse(text); // JSON 데이터가 있으면 파싱하여 반환
// }
// return true; // 응답 본문이 비어 있으면 true 반환
}
};
export const postMutiPart = async (url: string, data: FormData): Promise<any> => {
const options: RequestInit = {
method: "POST",
body: data,
};
const res = await fetch(`${BASE_URL}/${url}`, options);
const resData = await res.json();
return resData;
const options: RequestInit = {
method: "POST",
body: data,
};
const res = await fetch(`${BASE_URL}/${url}`, options);
const resData = await res.json();
return resData;
};
export const putData = async (url: string, data: object): Promise<any> => {
const options: RequestInit = {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
};
const res = await fetch(`${BASE_URL}/${url}`, options);
const resData = await res.json();
return resData;
const options: RequestInit = {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
};
const res = await fetch(`${BASE_URL}/${url}`, options);
const resData = await res.json();
return resData;
};
export const getData = async (url: string, data?: object): Promise<any> => {
const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, {
method: "GET",
});
if (res.ok) {
const data = await res.json();
return data;
}
throw new Error('Failed to fetch data');
const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, {
method: "GET",
});
if (res.ok) {
const data = await res.json();
return data;
}
throw new Error('Failed to fetch data');
};
export const deleteData = async (url: string, data?: object): Promise<any> => {
const options: RequestInit = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: data ? JSON.stringify(data) : undefined,
};
const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, options);
if (res.status === 204) return true
return res;
const options: RequestInit = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: data ? JSON.stringify(data) : undefined,
};
const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, options);
if (res.status === 204) return true
return res;
};
+16 -16
View File
@@ -1,24 +1,24 @@
import { getData } from "../api";
export const searchData = async<T extends Record<string, any>>(url: string, paramObj: T) => {
const params = new URLSearchParams(
Object.entries(paramObj)
.filter(([_, value]) => value !== undefined && value !== '') // 값이 있는 항목만 포함
.reduce((acc, [key, value]) => {
acc[key] = String(value);
return acc;
}, {} as Record<string, string>)
);
const data = await getData(`${url}?${params.toString()}`);
return data && { data: data.content, total: data.totalElements }
const params = new URLSearchParams(
Object.entries(paramObj)
.filter(([_, value]) => value !== undefined && value !== '') // 값이 있는 항목만 포함
.reduce((acc, [key, value]) => {
acc[key] = String(value);
return acc;
}, {} as Record<string, string>)
);
const data = await getData(`${url}?${params.toString()}`);
return data && { data: data.content, total: data.totalElements }
}
// 상위코드로 하위코드 조회
export const searchCmmnCd = async (upCd: string) => {
const data = await getData(`/cms-service/api/v1/code/cache/${upCd}`);
const filteredData = data.map((item: any) => ({
lwpoCd: item.lwpoCd,
lwpoCdNm: item.lwpoCdNm,
}));
return filteredData;
const data = await getData(`/cms-service/api/v1/code/cache/${upCd}`);
const filteredData = data.map((item: any) => ({
lwpoCd: item.lwpoCd,
lwpoCdNm: item.lwpoCdNm,
}));
return filteredData;
}
+10 -10
View File
@@ -1,13 +1,13 @@
export const formatDate = (date: Date): string => {
if (!date) return '';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
if (!date) return '';
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 월은 0부터 시작하므로 +1
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 현재 년도부터 10년 전까지의 년도 옵션 생성
export const yearOptions = Array.from({ length: 10 }, (_, i) => {
const year = new Date().getFullYear() - i;
return { text: `${year}년`, val: year.toString() };
});
// 현재 년도부터 10년 전까지의 년도 옵션 생성
export const yearOptions = Array.from({ length: 10 }, (_, i) => {
const year = new Date().getFullYear() - i;
return { text: `${year}년`, val: year.toString() };
});
+123 -123
View File
@@ -6,143 +6,143 @@ import { useId, useState } from 'react';
import type { HttpResponse, Upload } from "tus-js-client";
interface FileUploadFormProps {
onSubmit?: (files: string, year: string, quarter: string, code: string, id: string) => void;
onFileSelect?: (file: File | null, atflCd: string | null, atflId: string | null) => void; // 파일 선택 시 호출될 콜백 추가
showButton?: boolean; // 버튼 표시 여부 (기본값 true)
showPreview?: boolean; // 파일명 프리뷰 표시 여부
accept?: string; // 파일 형식 지정
fileIdx?: number; // 부모가 어떤 FileUploadForm인지 구분하기 위한 인덱스
onSubmit?: (files: string, year: string, quarter: string, code: string, id: string) => void;
onFileSelect?: (file: File | null, atflCd: string | null, atflId: string | null) => void; // 파일 선택 시 호출될 콜백 추가
showButton?: boolean; // 버튼 표시 여부 (기본값 true)
showPreview?: boolean; // 파일명 프리뷰 표시 여부
accept?: string; // 파일 형식 지정
fileIdx?: number; // 부모가 어떤 FileUploadForm인지 구분하기 위한 인덱스
}
const FileUploadForm = ({
onSubmit,
onFileSelect,
showButton = true,
showPreview = false,
accept = ".jpeg, .jpg, .png",
fileIdx // props에서 fileIdx 받기
onSubmit,
onFileSelect,
showButton = true,
showPreview = false,
accept = ".jpeg, .jpg, .png",
fileIdx // props에서 fileIdx 받기
}: FileUploadFormProps) => {
const [internalAtflCd, setInternalAtflCd] = useState<string | null>(null);
const [internalAtflId, setInternalAtflId] = useState<string | null>(null);
const [currentFile, setCurrentFile] = useState<File | null>(null); // 현재 선택된 파일을 저장할 상태
const [fileName, setFileName] = useState<string>('');
const [files, setFiles] = useState<FileList | null>(null);
const [upload, setUpload] = useState<Upload | null>(null);
const uniqueId = useId(); // 각 FileUploadForm 인스턴스에 대한 고유 ID 생성
const [internalAtflCd, setInternalAtflCd] = useState<string | null>(null);
const [internalAtflId, setInternalAtflId] = useState<string | null>(null);
const [currentFile, setCurrentFile] = useState<File | null>(null); // 현재 선택된 파일을 저장할 상태
const [fileName, setFileName] = useState<string>('');
const [files, setFiles] = useState<FileList | null>(null);
const [upload, setUpload] = useState<Upload | null>(null);
const uniqueId = useId(); // 각 FileUploadForm 인스턴스에 대한 고유 ID 생성
const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }];
// 현재 년도부터 10년 전까지의 년도 옵션 생성
const yearOptions = Array.from({ length: 10 }, (_, i) => {
const year = new Date().getFullYear() - i;
return { text: `${year}년`, val: year.toString() };
});
const [selectedYear, setSelectedYear] = useState<string>(yearOptions[0]?.val || '');
const [selectedQuarter, setSelectedQuarter] = useState<string>(quater[0]?.val || '');
const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }];
// 현재 년도부터 10년 전까지의 년도 옵션 생성
const yearOptions = Array.from({ length: 10 }, (_, i) => {
const year = new Date().getFullYear() - i;
return { text: `${year}년`, val: year.toString() };
});
const [selectedYear, setSelectedYear] = useState<string>(yearOptions[0]?.val || '');
const [selectedQuarter, setSelectedQuarter] = useState<string>(quater[0]?.val || '');
const internalHandleUploadResponse = (file: File) => async (res: HttpResponse) => {
const fileInfo = res.getHeader("X-File-Info");
if (!fileInfo) {
if (onFileSelect) onFileSelect(file, null, null);
return;
}
const { atflCd: newAtflCd, atflId: newAtflId } = JSON.parse(fileInfo) ?? {};
const orgnlFileNm = file.name;
console.log(`FileUploadForm (idx: ${fileIdx ?? 'N/A'}) - orgnlFileNm:`, orgnlFileNm, 'New AtflCd:', newAtflCd, 'New AtflId:', newAtflId);
setFileName(orgnlFileNm);
setInternalAtflCd(newAtflCd);
setInternalAtflId(newAtflId);
const internalHandleUploadResponse = (file: File) => async (res: HttpResponse) => {
const fileInfo = res.getHeader("X-File-Info");
if (!fileInfo) {
if (onFileSelect) onFileSelect(file, null, null);
return;
}
const { atflCd: newAtflCd, atflId: newAtflId } = JSON.parse(fileInfo) ?? {};
const orgnlFileNm = file.name;
console.log(`FileUploadForm (idx: ${fileIdx ?? 'N/A'}) - orgnlFileNm:`, orgnlFileNm, 'New AtflCd:', newAtflCd, 'New AtflId:', newAtflId);
setFileName(orgnlFileNm);
setInternalAtflCd(newAtflCd);
setInternalAtflId(newAtflId);
if (onFileSelect) {
onFileSelect(file, newAtflCd, newAtflId);
}
};
if (onFileSelect) {
onFileSelect(file, newAtflCd, newAtflId);
}
};
return (
<div> {/* 컴포넌트 루트 div */}
<div className="card p-6"> {/* 카드 스타일링 */}
<div className='flex items-center gap-2'> {/* FileUploader와 Button을 정렬하는 메인 flex 컨테이너 */}
<div className="w-[300px] mr-4"> {/* FileUploader의 너비와 오른쪽 여백을 제어하는 컨테이너 */}
<FileUploader
id={uniqueId}
className="!h-auto !max-w-none flex-row items-center gap-2"
accept={accept}
onUpload={(e) => {
const selectedFiles = e.target.files;
if (!selectedFiles || selectedFiles.length === 0) {
setFiles(null);
setCurrentFile(null);
setInternalAtflCd(null);
setInternalAtflId(null);
if (onFileSelect) {
onFileSelect(null, null, null); // 파일 선택 취소 시 null 전달
}
return;
}
return (
<div> {/* 컴포넌트 루트 div */}
<div className="card p-6"> {/* 카드 스타일링 */}
<div className='flex items-center gap-2'> {/* FileUploader와 Button을 정렬하는 메인 flex 컨테이너 */}
<div className="w-[300px] mr-4"> {/* FileUploader의 너비와 오른쪽 여백을 제어하는 컨테이너 */}
<FileUploader
id={uniqueId}
className="!h-auto !max-w-none flex-row items-center gap-2"
accept={accept}
onUpload={(e) => {
const selectedFiles = e.target.files;
if (!selectedFiles || selectedFiles.length === 0) {
setFiles(null);
setCurrentFile(null);
setInternalAtflCd(null);
setInternalAtflId(null);
if (onFileSelect) {
onFileSelect(null, null, null); // 파일 선택 취소 시 null 전달
}
return;
}
const firstFile = selectedFiles[0];
if (!firstFile) return; // 타입스크립트 undefined 가능성 제거
const firstFile = selectedFiles[0];
if (!firstFile) return; // 타입스크립트 undefined 가능성 제거
setFiles(selectedFiles);
setCurrentFile(firstFile);
setFiles(selectedFiles);
setCurrentFile(firstFile);
if (onFileSelect) {
onFileSelect(firstFile, internalAtflCd, internalAtflId);
}
if (onFileSelect) {
onFileSelect(firstFile, internalAtflCd, internalAtflId);
}
handleUpload(selectedFiles, {
handleUploadStart: setUpload,
handleSuccess: setUpload,
// handleResponse는 tus-js-client의 Upload 인스턴스 콜백과 연동 필요
// 여기서는 handleUpload 유틸리티가 응답을 직접 처리한다고 가정
handleResponse: internalHandleUploadResponse(firstFile),
});
}}
customPreview={
showPreview ? (
<div className="file-uploader-preview">
{files && files.length > 0
? fileName
: "파일을 선택하세요."}
</div>
) : null
}
/>
</div>
{showButton ? ( // showButton이 true일 때만 버튼 렌더링
<Button
className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700"
size="sm"
onClick={() => {
console.log(files?.[0]?.name)
if (!currentFile) {
alert('파일을 선택해주세요.');
return;
}
if (!selectedYear) {
alert('등록년도를 선택해주세요.');
return;
}
if (!selectedQuarter) {
alert('분기를 선택해주세요.');
return;
}
const fileNm = currentFile.name;
if (onSubmit) {
// 부모에게 전달할 때는 내부 상태인 internalAtflCd, internalAtflId 사용
onSubmit(fileNm, selectedYear, selectedQuarter, internalAtflCd || '', internalAtflId || '');
setFiles(null); // 파일 업로드 후 파일 목록 초기화
setFileName(''); // 파일명 초기화
}
}}
>
등록
</Button>
) : null}
</div>
</div>
handleUpload(selectedFiles, {
handleUploadStart: setUpload,
handleSuccess: setUpload,
// handleResponse는 tus-js-client의 Upload 인스턴스 콜백과 연동 필요
// 여기서는 handleUpload 유틸리티가 응답을 직접 처리한다고 가정
handleResponse: internalHandleUploadResponse(firstFile),
});
}}
customPreview={
showPreview ? (
<div className="file-uploader-preview">
{files && files.length > 0
? fileName
: "파일을 선택하세요."}
</div>
) : null
}
/>
</div>
{showButton ? ( // showButton이 true일 때만 버튼 렌더링
<Button
className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700"
size="sm"
onClick={() => {
console.log(files?.[0]?.name)
if (!currentFile) {
alert('파일을 선택해주세요.');
return;
}
if (!selectedYear) {
alert('등록년도를 선택해주세요.');
return;
}
if (!selectedQuarter) {
alert('분기를 선택해주세요.');
return;
}
const fileNm = currentFile.name;
if (onSubmit) {
// 부모에게 전달할 때는 내부 상태인 internalAtflCd, internalAtflId 사용
onSubmit(fileNm, selectedYear, selectedQuarter, internalAtflCd || '', internalAtflId || '');
setFiles(null); // 파일 업로드 후 파일 목록 초기화
setFileName(''); // 파일명 초기화
}
}}
>
등록
</Button>
) : null}
</div>
)
</div>
</div>
)
}
export default FileUploadForm
+14 -14
View File
@@ -2,23 +2,23 @@ import { FormField } from '@common/business_components/ui'
import React, { type ReactElement } from 'react'
interface FormSectionProps {
children: ReactElement
titleNm: string
form: any
name: string
children: ReactElement
titleNm: string
form: any
name: string
}
const FormSection = ({ children, titleNm, form, name }: FormSectionProps) => {
return (
<div className="grid grid-cols-[200px_1fr] items-center">
<div className="text-xl font-medium">{titleNm}</div>
<FormField
control={form.control}
name={name}
render={({ field }) => children}
/>
</div>
)
return (
<div className="grid grid-cols-[200px_1fr] items-center">
<div className="text-xl font-medium">{titleNm}</div>
<FormField
control={form.control}
name={name}
render={({ field }) => children}
/>
</div>
)
}
export default FormSection
+26 -26
View File
@@ -2,34 +2,34 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogC
import React from 'react'
interface ModalProps {
isModalOpen: boolean;
setIsModalOpen: (open: boolean) => void;
title?: string;
children: React.ReactNode;
closeModal: () => void;
saveModal?: () => void
isModalOpen: boolean;
setIsModalOpen: (open: boolean) => void;
title?: string;
children: React.ReactNode;
closeModal: () => void;
saveModal?: () => void
}
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal,saveModal }) => {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogOverlay className='z-[200]' />
<DialogContent className="w-auto max-w-[1200px] min-w-[400px] min-h-[150px] h-auto max-h-[450px] overflow-auto z-[1000]">
{title ? <DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader> : null}
{children}
<DialogFooter>
<DialogClose>
<div className='flex justify-end gap-2'>
{saveModal ? <button className="px-4 py-2 bg-primary text-white rounded" onClick={saveModal}>저장</button> : null}
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closeModal}>닫기</button>
</div>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
const Modal: React.FC<ModalProps> = ({ isModalOpen, setIsModalOpen, title, children, closeModal, saveModal }) => {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogOverlay className='z-[200]' />
<DialogContent className="w-auto max-w-[1200px] min-w-[400px] min-h-[150px] h-auto max-h-[450px] overflow-auto z-[1000]">
{title ? <DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader> : null}
{children}
<DialogFooter>
<DialogClose>
<div className='flex justify-end gap-2'>
{saveModal ? <button className="px-4 py-2 bg-primary text-white rounded" onClick={saveModal}>저장</button> : null}
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closeModal}>닫기</button>
</div>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
export default Modal
@@ -4,48 +4,48 @@ import React, { useState } from 'react'
import FileUploadForm from './fileUploadForm';
import { yearOptions } from '@/components/lib/utils';
const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: string, year: string, quarter: string, code:string, id:string) => void }) => {
const [atflCd, setAtflCd] = useState<string | null>(null);
const [atflId, setAtflId] = useState<string | null>(null);
const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: string, year: string, quarter: string, code: string, id: string) => void }) => {
const [atflCd, setAtflCd] = useState<string | null>(null);
const [atflId, setAtflId] = useState<string | null>(null);
const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }]
const [selectedYear, setSelectedYear] = useState<string>(yearOptions[0]?.val || '');
const [selectedQuarter, setSelectedQuarter] = useState<string>(quater[0]?.val || '');
const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }]
const [selectedYear, setSelectedYear] = useState<string>(yearOptions[0]?.val || '');
const [selectedQuarter, setSelectedQuarter] = useState<string>(quater[0]?.val || '');
return (
<div>
<div className='card-header'>
<h3 className="text-2xl font-medium">{title}</h3>
</div>
<div className="card p-6">
<div className="grid grid-col-2 items-center space-x-4">
<div className='flex'>
<SelectBox
className="w-50 mr-3 ml-4"
selectClass="rounded-lg bg-white"
label="년도"
selectArray={yearOptions}
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
/><SelectBox
className="w-50"
selectClass="rounded-lg bg-white"
label="분기"
selectArray={quater}
onSelectValue={(value: string | number) => setSelectedQuarter(String(value))}
/>
<FileUploadForm
onSubmit={(files, year, quarter, code, id) => {
onSubmit(files, (selectedYear ?? year) || '', (selectedQuarter ?? quarter) || '', code, id);
}}
showButton={true}
showPreview={true}
accept={""} />
</div>
</div>
</div>
return (
<div>
<div className='card-header'>
<h3 className="text-2xl font-medium">{title}</h3>
</div>
<div className="card p-6">
<div className="grid grid-col-2 items-center space-x-4">
<div className='flex'>
<SelectBox
className="w-50 mr-3 ml-4"
selectClass="rounded-lg bg-white"
label="년도"
selectArray={yearOptions}
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
/><SelectBox
className="w-50"
selectClass="rounded-lg bg-white"
label="분기"
selectArray={quater}
onSelectValue={(value: string | number) => setSelectedQuarter(String(value))}
/>
<FileUploadForm
onSubmit={(files, year, quarter, code, id) => {
onSubmit(files, (selectedYear ?? year) || '', (selectedQuarter ?? quarter) || '', code, id);
}}
showButton={true}
showPreview={true}
accept={""} />
</div>
</div>
)
</div>
</div>
)
}
export default operFileUploadForm
+188 -188
View File
@@ -3,204 +3,204 @@ import { Table, TableBody, TableCell, TableRow } from "@common/components";
import { useEffect, useState } from "react";
type UiTableProps = {
headName: React.ReactNode | undefined;
tableName1?: React.ReactNode | undefined;
tableData: { [key: string]: string | number | boolean | object }[];
headlist: UiHeadProps[];
pageSize: number;
total: number;
children: Record<string, React.ReactNode> | undefined;
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
onPageChange: (page: number) => void;
className?: string;
resetPageNo?: boolean;
isEditable?: boolean;
tableName2?: React.ReactNode | undefined;
tableData2?: { [key: string]: string | number | boolean | object }[];
headlist2?: UiHeadProps[];
pageSize2?: number;
total2?: number;
children2?: Record<string, React.ReactNode> | undefined;
cellClick2?: ((index: number | undefined, headId: string) => void) | undefined;
onPageChange2?: (page: number) => void;
className2?: string;
resetPageNo2?: boolean;
isEditable2?: boolean;
showSecondTable?: boolean;
headName: React.ReactNode | undefined;
tableName1?: React.ReactNode | undefined;
tableData: { [key: string]: string | number | boolean | object }[];
headlist: UiHeadProps[];
pageSize: number;
total: number;
children: Record<string, React.ReactNode> | undefined;
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
onPageChange: (page: number) => void;
className?: string;
resetPageNo?: boolean;
isEditable?: boolean;
tableName2?: React.ReactNode | undefined;
tableData2?: { [key: string]: string | number | boolean | object }[];
headlist2?: UiHeadProps[];
pageSize2?: number;
total2?: number;
children2?: Record<string, React.ReactNode> | undefined;
cellClick2?: ((index: number | undefined, headId: string) => void) | undefined;
onPageChange2?: (page: number) => void;
className2?: string;
resetPageNo2?: boolean;
isEditable2?: boolean;
showSecondTable?: boolean;
};
type UiHeadProps = {
id: string;
title: string;
align?: string;
type?: string;
options?: string[];
id: string;
title: string;
align?: string;
type?: string;
options?: string[];
};
export function VerticalTable({
headName,
tableName1,
tableData,
headlist,
pageSize,
total,
children,
cellClick,
onPageChange,
className = "",
resetPageNo = false,
isEditable = false,
tableName2,
tableData2,
headlist2,
pageSize2,
total2,
children2,
cellClick2,
onPageChange2,
className2 = "",
resetPageNo2 = false,
isEditable2 = false,
showSecondTable = false,
headName,
tableName1,
tableData,
headlist,
pageSize,
total,
children,
cellClick,
onPageChange,
className = "",
resetPageNo = false,
isEditable = false,
tableName2,
tableData2,
headlist2,
pageSize2,
total2,
children2,
cellClick2,
onPageChange2,
className2 = "",
resetPageNo2 = false,
isEditable2 = false,
showSecondTable = false,
}: UiTableProps) {
const [cur, setCur] = useState<number>(1);
const [formData, setFormData] = useState<{ [key: string]: string | number | boolean }>({});
useEffect(() => {
if (resetPageNo) {
setCur(1);
onPageChange(1);
}
}, [onPageChange, resetPageNo]);
const [cur, setCur] = useState<number>(1);
const [formData, setFormData] = useState<{ [key: string]: string | number | boolean }>({});
useEffect(() => {
if (resetPageNo) {
setCur(1);
onPageChange(1);
}
}, [onPageChange, resetPageNo]);
useEffect(() => {
if (tableData.length > 0 && tableData[0]) {
setFormData(tableData[0] as { [key: string]: string | number | boolean });
}
}, [tableData]);
useEffect(() => {
if (tableData.length > 0 && tableData[0]) {
setFormData(tableData[0] as { [key: string]: string | number | boolean });
}
}, [tableData]);
const handleInputChange = (id: string, value: string | number | boolean) => {
setFormData((prev) => ({
...prev,
[id]: value,
}));
};
const handleInputChange = (id: string, value: string | number | boolean) => {
setFormData((prev) => ({
...prev,
[id]: value,
}));
};
const handlePageChange = (page: number) => {
setCur(page);
onPageChange(page);
};
const handlePageChange = (page: number) => {
setCur(page);
onPageChange(page);
};
return (
<>
{headName ? (
<div className="card-header">
<h3 className="text-2xl mx-3 font-medium text-label">{headName}</h3>
</div>
) : null}
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
{tableName1 && ( // tableName prop이 있으면 표시
<div className="text-xl font-semibold my-2 mx-3">{tableName1}</div>
)}
<Table variant="secondary">
<TableBody className="text-center">
{headlist?.map((head) => (
<TableRow key={head.id}>
{/* 컬럼 제목 */}
<TableCell className="font-bold bg-blue-100 text-xs">
{head.title}
</TableCell>
{/* 데이터 값 또는 입력 필드 */}
<TableCell className="text-xs">
{children && children[head.id] ? children[head.id] : isEditable ? (
head.type === "select" ? (
<select
id={head.id}
value={formData[head.id] ?? "-"}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
>
{head.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
) : head.type === "input" ? (
<input
id={head.id}
type="text"
value={formData[head.id] ?? ""}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
/>
) : (
<span>{formData[head.id] ?? "-"}</span>
)
) : (
<span>{formData[head.id] ?? "-"}</span>
)}
</TableCell>
</TableRow>
return (
<>
{headName ? (
<div className="card-header">
<h3 className="text-2xl mx-3 font-medium text-label">{headName}</h3>
</div>
) : null}
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
{tableName1 && ( // tableName prop이 있으면 표시
<div className="text-xl font-semibold my-2 mx-3">{tableName1}</div>
)}
<Table variant="secondary">
<TableBody className="text-center">
{headlist?.map((head) => (
<TableRow key={head.id}>
{/* 컬럼 제목 */}
<TableCell className="font-bold bg-blue-100 text-xs">
{head.title}
</TableCell>
{/* 데이터 값 또는 입력 필드 */}
<TableCell className="text-xs">
{children && children[head.id] ? children[head.id] : isEditable ? (
head.type === "select" ? (
<select
id={head.id}
value={formData[head.id] ?? "-"}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
>
{head.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</TableBody>
</Table>
{tableName2 && ( // tableName prop이 있으면 표시
<div className="text-xl font-semibold mt-8 my-2 mx-3">{tableName2}</div>
)}
{showSecondTable && (
<Table variant="secondary">
<TableBody className="text-center">
{headlist2?.map((head) => (
<TableRow key={head.id}>
{/* 컬럼 제목 */}
<TableCell className="font-bold bg-blue-100 text-xs w-fit whitespace-nowrap">
{head.title}
</TableCell>
{/* 데이터 값 또는 입력 필드 */}
<TableCell className="text-xs">
{children && children[head.id] ? children[head.id] : isEditable2 ? (
head.type === "select" ? (
<select
id={head.id}
value={formData[head.id] ?? "-"}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
>
{head.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
) : head.type === "input" ? (
<input
id={head.id}
type="text"
value={formData[head.id] ?? ""}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
/>
) : (
<span>{formData[head.id] ?? "-"}</span>
)
) : (
<span>{formData[head.id] ?? "-"}</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{total > 0 && (
<UiPageNations
total={total}
cur={cur}
pageSize={pageSize}
onPageChange={handlePageChange}
/>
)}
</div>
</>
);
</select>
) : head.type === "input" ? (
<input
id={head.id}
type="text"
value={formData[head.id] ?? ""}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
/>
) : (
<span>{formData[head.id] ?? "-"}</span>
)
) : (
<span>{formData[head.id] ?? "-"}</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{tableName2 && ( // tableName prop이 있으면 표시
<div className="text-xl font-semibold mt-8 my-2 mx-3">{tableName2}</div>
)}
{showSecondTable && (
<Table variant="secondary">
<TableBody className="text-center">
{headlist2?.map((head) => (
<TableRow key={head.id}>
{/* 컬럼 제목 */}
<TableCell className="font-bold bg-blue-100 text-xs w-fit whitespace-nowrap">
{head.title}
</TableCell>
{/* 데이터 값 또는 입력 필드 */}
<TableCell className="text-xs">
{children && children[head.id] ? children[head.id] : isEditable2 ? (
head.type === "select" ? (
<select
id={head.id}
value={formData[head.id] ?? "-"}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full border border-gray-300 rounded-sm px-2 py-1 text-xl border-radius-0"
>
{head.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
) : head.type === "input" ? (
<input
id={head.id}
type="text"
value={formData[head.id] ?? ""}
onChange={(e) => handleInputChange(head.id, e.target.value)}
className="w-full text-xl border-radius-0 border border-gray-300 rounded-sm px-2 py-1"
/>
) : (
<span>{formData[head.id] ?? "-"}</span>
)
) : (
<span>{formData[head.id] ?? "-"}</span>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
{total > 0 && (
<UiPageNations
total={total}
cur={cur}
pageSize={pageSize}
onPageChange={handlePageChange}
/>
)}
</div>
</>
);
}