dm: 수요정보 등록 유사사례 관련 주석 추가

This commit is contained in:
2026-05-07 15:39:28 +09:00
parent c2f8bd5a83
commit 9ac72c111b
4 changed files with 96 additions and 53 deletions
@@ -1,10 +1,12 @@
// apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx (유사사례 모달)
/**
* 유사사례 Modal
*/
"use client";
import { useEffect, useMemo, useState, useId } from "react";
import { createPortal } from "react-dom";
type Criterion = "" | "supplyAmt" | "treatment" | "usePurpose";
type Criterion = "" | "supplyAmt" | "treatment" | "usePurpose"; // 유사기준
export type SimilarCaseRow = {
caseId: string;
@@ -90,7 +92,7 @@ export default function SimilarCaseModal({
// 모달 열릴 때 초기화
useEffect(() => {
if (!open) return;
setCriterion("supplyAmt");
setCriterion("supplyAmt"); // 요구수량(㎥/d)
onSearch("supplyAmt");
setSelectedCaseId("");
}, [open]);
@@ -36,9 +36,9 @@ export default function DemandRegisterPage() {
// 기본정보
const [reqDate, setReqDate] = useState<string>(today);
const [reqQty, setReqQty] = useState<string>("");
const [usePurpose, setUsePurpose] = useState<string>("");
const [hopePrice, setHopePrice] = useState<string>("");
const [reqQty, setReqQty] = useState<string>(""); // 요구수량(㎥/d)
const [usePurpose, setUsePurpose] = useState<string>(""); // 사용용도
const [hopePrice, setHopePrice] = useState<string>(""); // 희망단가
// 요구수질
const [coliform, setColiform] = useState<string>(""); // 총대장균군수
@@ -62,9 +62,9 @@ export default function DemandRegisterPage() {
const [cand3, setCand3] = useState("");
//const [candidateList, setCandidateList] = useState<SubsInfo[]>([]);
const useTypesData = useDmStore((state) => state.Sdm.useTypesData);
const getUseTypesData = useDmStore((state) => state.getUseTypesData);
const [openSimilar, setOpenSimilar] = useState(false);
const useTypesData = useDmStore((state) => state.Sdm.useTypesData); // 사용용도
const getUseTypesData = useDmStore((state) => state.getUseTypesData); // 사용용도 함수
const [openSimilar, setOpenSimilar] = useState(false); // 유사사례 modal open
// 후보지 팝업 메시지 핸들러
const candsRef = useRef<{ name: string; code: string }[]>([]);
@@ -114,15 +114,16 @@ export default function DemandRegisterPage() {
const getSimilarCaseList = useDmStore((state) => state.getSimilarCaseList);
const [farmText, setFarmText] = useState("이하");
const insertSdmInfoData = useDmStore(
(state) => state.insertSdmInfoData
);
const insertSdmInfoData = useDmStore( (state) => state.insertSdmInfoData ); // 수요정보등록
useEffect(() => {
getUseTypesData();
getUseTypesData(); // 사용용도 조회
}, [getUseTypesData]);
// 후보지 선택 팝업이 window.postMessage로 넘긴 선택 결과를 받아
// 현재 화면의 후보지 입력칸(cand1~3)에 반영한다.
useEffect(() => {
// message 이벤트의 payload를 읽어 후보지 목록 상태를 갱신하는 핸들러
const handler = (event: MessageEvent) => {
if (!event.data) return;
@@ -199,25 +200,26 @@ export default function DemandRegisterPage() {
return true;
}
// 유사사례
const rows: SimilarCaseRow[] = similarCaseList.map((el, idx) => ({
caseId: String(idx),
projectName: el.ruseBizNm,
period: `${el.bizBgngDate} ~ ${el.bizEndDate}`,
projectName: el.ruseBizNm, // 사업명
period: `${el.bizBgngDate} ~ ${el.bizEndDate}`, // 사업기간
supplyFacilityType: el.splyFcltKnd,
supplyName: el.splyFcltNm,
supplyCapacity: Number(el.splyFcltCpct),
supplyFacilityType: el.splyFcltKnd, // 시설종류
supplyName: el.splyFcltNm, // 공급처명
supplyCapacity: Number(el.splyFcltCpct), // 공급처 시설용량
demandName: el.dmdFcltNm,
demandAddress: el.dmdFcltAddr,
demandName: el.dmdFcltNm, // 수요처명
demandAddress: el.dmdFcltAddr, // 수요처 주소
facilityCapacity: Number(el.ruseFcltCpct),
pipeKm: Number(el.ruseFcltPplLen),
purpose: el.ruseFcltUsg,
facilityCapacity: Number(el.ruseFcltCpct), // 재이용시설 시설용량
pipeKm: Number(el.ruseFcltPplLen), // 재이용시설 관로길이
purpose: el.ruseFcltUsg, // 재이용시설 용도
supplyAmt: Number(el.ruseFcltSplyCpct),
treatment: el.ruseFcltPrcsMsrc,
usePurpose: el.ruseFcltUsg,
supplyAmt: Number(el.ruseFcltSplyCpct), // 재이용시설 공급(용)량
treatment: el.ruseFcltPrcsMsrc, // 재이용시설 처리공법
usePurpose: el.ruseFcltUsg, // 재이용시설 용도
}));
const detailsByCaseId: Record<string, SimilarCaseDetail> = {};
@@ -311,6 +313,7 @@ export default function DemandRegisterPage() {
}
}
// 후보지 조회 팝업 Open
const searchCandidate = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -327,28 +330,27 @@ export default function DemandRegisterPage() {
window.open(url, "candidatePopup", popupOptions);
};
// 유사기준
const getCriterionCode = (criterion: string) => {
switch (criterion) {
case "supplyAmt":
return "S";
case "money":
return "M";
case "usePurpose":
return "U";
default:
return "";
case "supplyAmt": return "S"; // 공급량
case "money": return "M"; // 단가?
case "usePurpose": return "U"; // 이용용도
default: return "";
}
};
// 유사사례 Modal Open
const searchSimilarCase = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setOpenSimilar(true);
};
// 수요정보 입력란 초기화
const initQualityForm = () => {
setColiform("");
setNoDetect(false);
setNoDetect(false); // 총대장균군수 불검출
setTn("");
setResidualCl("");
setTp("");
@@ -363,6 +365,7 @@ export default function DemandRegisterPage() {
setFarmText("이하");
}
// 사용용도별 초기화
const fncSetOptionValue = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
initQualityForm();
@@ -640,19 +643,20 @@ export default function DemandRegisterPage() {
{/* 후보지 선택 */}
<div className="grid grid-cols-12">
<LabelCell>
공급처 후보지 선택
<br />
(필수아님)
<span className="flex flex-col items-center leading-6">
<span>공급처 후보지 선택</span>
<span className="text-sm">(필수아님)</span>
</span>
</LabelCell>
<div className="col-span-12 md:col-span-10 p-6">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4">
<TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" />
<TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" />
<TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" />
<div className="flex items-center gap-3 xl:ml-2">
<button
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700"
onClick={(e) => {
@@ -660,22 +664,20 @@ export default function DemandRegisterPage() {
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
}}>
}}
>
후보지 조회
</button>
</button>
<button
<button
type="button"
className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600"
onClick={(e) => {
// TODO: 유사사례 연결
searchSimilarCase(e);
}}
>
onClick={(e) => searchSimilarCase(e)}
>
유사사례
</button>
</div>
</button>
</div>
</div>
</div>
</div>
</div>
@@ -703,7 +705,7 @@ export default function DemandRegisterPage() {
rows={rows} // 실제 데이터
detailsByCaseId={detailsByCaseId}
onSearch={async (criterion) => {
const code = getCriterionCode(criterion);
const code = getCriterionCode(criterion); // 'S' 공급량, 'U' 사용용도
if (!code) return;
// 값은 상황에 맞게 (예시: usePurpose)
console.log(code+' req: '+reqQty+' purpose :'+usePurpose);
@@ -6,12 +6,15 @@
import { useDmStore } from "@/app/store";
import type { DemandCompBizType } from "@/app/store/sdmMng";
import SubPageHeader from "@/components/SubPageHeader";
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
import { useEffect, useMemo, useRef, useState } from "react";
type TopField = { label: string; key: string, value: string; required?: boolean };
type PairField = { label: string; key: string, value: string };
type SessionUser = { dmdInfoId?: string };
const PAGE_SIZE = 10;
const formatDate = (value: string | Date | "" | undefined) => {
if (!value) return "-";
const date = new Date(value);
@@ -135,6 +138,7 @@ export default function DemandStatusPage() {
const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false);
const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState<number | "" | null>(null);
const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState<DemandCompBizType[]>([]);
const [currentPage, setCurrentPage] = useState(1);
// 수정 유무
const [ isEditMode, setIsEditMode ] = useState(false);
@@ -213,6 +217,18 @@ export default function DemandStatusPage() {
);
}, [transformedSdmInfoStatusList, selectedDmdInfoSn]);
const pagedSdmInfoStatusList = useMemo(() => {
const startIndex = (currentPage - 1) * PAGE_SIZE;
return transformedSdmInfoStatusList.slice(startIndex, startIndex + PAGE_SIZE);
}, [currentPage, transformedSdmInfoStatusList]);
useEffect(() => {
const totalPages = Math.max(1, Math.ceil(transformedSdmInfoStatusList.length / PAGE_SIZE));
if (currentPage > totalPages) {
setCurrentPage(totalPages);
}
}, [currentPage, transformedSdmInfoStatusList.length]);
const topFields: TopField[] = useMemo(() => {
if (!selectedSdmInfoStatus) return [];
@@ -392,7 +408,7 @@ export default function DemandStatusPage() {
</td>
</tr>
) : (
transformedSdmInfoStatusList.map((sdmInfoStatus, index) => {
pagedSdmInfoStatusList.map((sdmInfoStatus, index) => {
const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn;
return (
<tr
@@ -413,7 +429,9 @@ export default function DemandStatusPage() {
].join(" ")}
aria-selected={active}
>
<td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td>
<td className="px-3 py-3 text-center">
{sdmInfoStatus.index || (currentPage - 1) * PAGE_SIZE + index + 1}
</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td>
<td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td>
@@ -432,6 +450,25 @@ export default function DemandStatusPage() {
</tbody>
</table>
</div>
{transformedSdmInfoStatusList.length > 0 ? (
<div
className={[
"mt-2 flex justify-center",
"[&_nav]:mt-0 [&_nav]:text-sm",
"[&_ul]:gap-0.5",
"[&_a]:h-8 [&_a]:min-w-8 [&_a]:px-2 [&_a]:py-1 [&_a]:text-sm",
"[&_svg]:h-4 [&_svg]:w-4",
"[&_span]:text-sm",
].join(" ")}
>
<UiPageNations
total={transformedSdmInfoStatusList.length}
cur={currentPage}
pageSize={PAGE_SIZE}
onPageChange={setCurrentPage}
/>
</div>
) : null}
</section>
<section className="mt-10 pb-16" ref={detailRef}>
+2
View File
@@ -537,6 +537,8 @@ const getSdmInfoStatusList = async (dmdInfoId: string) => {
// 유사사례 조회
const fetchGetSimilarCaseList = async (part: string, value: number | string) => {
try {
// part : 'S' 요구수량 , 'U' 사용용도
// default : part 'S' , value 10000 즉, part와 value값이 없으면 backend에서 default값으로 조회함.
const url = '/reuseFc/pubFcRuseCase/dm/' + part + '/' + (value ? value : 0);
const res = await getFetch(fetchType.WR, url);
return res?.data || [];