This commit is contained in:
2025-08-04 10:46:41 +09:00
144 changed files with 5724 additions and 3597 deletions
+2 -2
View File
@@ -52,9 +52,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
if (await confirm(confirmMsg)) {
if (linkSn) {
updateLinkInfo?.(data);
} else {
} else {
handleInsertLink?.(data);
}
}
}
});
};
@@ -66,7 +66,7 @@ function LinkParamsPage() {
const userUniqId = getCookie(AUTH_USER_ID) ?? "";
const { setMessage: alert } = useAlertStore((state) => state);
const dbIdList = ['link0001', 'link0002'];
const dbIdList = ['link0001', 'link0002', 'link0007'];
// Link 목록 쿼리
const query = useQuery({
@@ -7,7 +7,7 @@ import ListDiv from '@common/business_components/ui/list_div';
import { useEffect, useState } from 'react';
import { Toaster } from 'react-hot-toast';
import SdmLogin from '../sdmLogin/page';
import type { DemandCompType } from '@/app/store/sdmMng';
import type { DemandCompBizType } from '@/app/store/sdmMng';
import TransValueFormat from '@/app/common/transValueFormat';
import { useRouter } from 'next/navigation';
@@ -31,8 +31,8 @@ export default function SdmApplyList({}: SdmApplyProps) {
const {selectSidoData, selectSigunData} = Common;
const [isLogin, setIsLogin] = useState(false);
const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false);
const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompType>({} as DemandCompType);
const [transformedList, setTransformedList] = useState<DemandCompType[]>([]);
const [sdmApplyDetail, setSdmApplyDetail] = useState<DemandCompBizType>({} as DemandCompBizType);
const [transformedList, setTransformedList] = useState<DemandCompBizType[]>([]);
useEffect(() => {
console.log('sessionStorage : ',sessionStorage.getItem('user'));
@@ -60,10 +60,10 @@ export default function SdmApplyList({}: SdmApplyProps) {
// 데이터 변환을 위한 별도의 useEffect
useEffect(() => {
if (sdmInfoStatusList.length > 0) {
const newTransformedList = sdmInfoStatusList.map((item: DemandCompType) => ({
const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({
...item,
ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '',
sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || ''
sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '',
}));
setTransformedList(newTransformedList);
}
@@ -73,6 +73,8 @@ export default function SdmApplyList({}: SdmApplyProps) {
if (index !== undefined) {
const selectedStatus = transformedList[index];
if (selectedStatus) {
setSdmApplyDetail(selectedStatus);
}
}
@@ -121,33 +123,33 @@ export default function SdmApplyList({}: SdmApplyProps) {
</TableRow>
<TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.grssClfg} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.grssClfg!=-1?<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>:''} {sdmApplyDetail?.grssClfg?sdmApplyDetail.grssClfg!=-1? '이하':'불검출':'-'} </TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>} {sdmApplyDetail?.combResdch?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>} {sdmApplyDetail?.trbt?'이하':'-'}</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>} {sdmApplyDetail?.blgcOxygDmndQnt?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>} {sdmApplyDetail?.smll?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>} {sdmApplyDetail?.crmty?'이하':'-'}</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>} {sdmApplyDetail?.tn?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>} {sdmApplyDetail?.tph?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>} {sdmApplyDetail?.clIonQnt?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv} format="number"/>}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>} {sdmApplyDetail?.cdtnv?'이하':'-'}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell>
</TableRow>
@@ -156,11 +158,11 @@ export default function SdmApplyList({}: SdmApplyProps) {
</TableRow>
<TableRow>
<TableCell colSpan={1} className={TABLE_STYLES.header}>후보지1</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.splyCndtList?.[0]?.ruseUtztnFcltNm ?? ""}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>후보지2</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[1]?.ruseUtztnFcltNm ?? ""}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>후보지3</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}></TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[2]?.ruseUtztnFcltNm ?? ""}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -177,7 +179,7 @@ export default function SdmApplyList({}: SdmApplyProps) {
<TableCell colSpan={1} className={TABLE_STYLES.header}>진행상태</TableCell>
<TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.status?.toString()}</TableCell>
<TableCell colSpan={1} className={TABLE_STYLES.header}>공급처매칭결과</TableCell>
<TableCell colSpan={2} className={TABLE_STYLES.cell}></TableCell>
<TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.ruseUtztnFcltNm??''}</TableCell>
</TableRow>
</TableBody>
</Table>
@@ -1,7 +1,7 @@
"use client";
import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng";
import { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } from "@/app/store/sdmMng";
import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component";
import { UiModal } from "@common/business_components/ui/dialog_ui";
import { Button } from "@common/components/ui";
@@ -9,229 +9,207 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter, useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
import { FormProvider, useForm, type SubmitHandler } from "react-hook-form";
import { Toaster } from "react-hot-toast";
import * as z from "zod";
import toast, { Toaster } from "react-hot-toast";
import SdmLogin from "../sdmLogin/page";
const sdmInfoFormSchema = z.object({
sdmNm: z.string()
.min(1, "수요처를 선택해주세요"),
demandYmd: z.date().optional(),
demandQnty: z.string()
.min(1, "요구수량을 입력해주세요")
.regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"),
usePurpose: z.string().optional(),
money: z.string()
.regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"),
/**요구수질 */
reqWaterQlty: z.string().optional(),
grssClfg: z.string()
.min(1, "총대장균군수를 입력해주세요")
.regex(/^[0-9]+$/, "총대장균군수는 숫자만 입력 가능합니다"),
tn: z.string()
.min(1, "총질소를 입력해주세요")
.regex(/^[0-9]+$/, "총질소는 숫자만 입력 가능합니다"),
combResdch: z.string()
.min(1, "결합잔류염소를 입력해주세요")
.regex(/^[0-9]+$/, "결합잔류염소는 숫자만 입력 가능합니다"),
tph: z.string()
.min(1, "총인산물을 입력해주세요")
.regex(/^[0-9]+$/, "총인산물은 숫자만 입력 가능합니다"),
trbt: z.string()
.min(1, "탁도를 입력해주세요")
.regex(/^[0-9]+$/, "탁도는 숫자만 입력 가능합니다"),
ph: z.string()
.min(1, "수소이온농도를 입력해주세요")
.regex(/^[0-9]+$/, "수소이온농도는 숫자만 입력 가능합니다"),
bod: z.string()
.min(1, "생물학적 산소요구량을 입력해주세요")
.regex(/^[0-9]+$/, "생물학적 산소요구량은 숫자만 입력 가능합니다"),
/**컬럼확인필요 */
clIonQnt: z.string()
.min(1, "염화물을 입력해주세요")
.regex(/^[0-9]+$/, "염화물은 숫자만 입력 가능합니다"),
smll: z.string()
.min(1, "냄새를 입력해주세요")
.regex(/^[0-9]+$/, "냄새는 숫자만 입력 가능합니다"),
cdtnv: z.string()
.min(1, "전기전도도를 입력해주세요")
.regex(/^[0-9]+$/, "전기전도도는 숫자만 입력 가능합니다"),
crmty: z.string()
.min(1, "색도를 입력해주세요")
.regex(/^[0-9]+$/, "색도는 숫자만 입력 가능합니다"),
rmrk: z.string().optional(),
/* telno: z.string()
.min(8, "전화번호는 8자 이상 입력해주세요")
.max(12, "전화번호는 12자 이내로 입력해주세요")
.regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */
candidateTitle: z.array(z.object({
ruseUtztnFcltNm: z.string(),
ruseUtztnFcltCd: z.string(),
})).optional(),
});
export type FormData = z.infer<typeof sdmInfoFormSchema>;
function SdmInfoInsertContent() {
const searchParams = useSearchParams();
const {insertSdmInfoData, Sdm ,setSubsInfo} = useDmStore((state) => state);
const { insertSdmInfoData, Sdm, setSubsInfo } = useDmStore((state) => state);
const { successLogin } = Sdm;
const [ismoneyDisabled, setIsmoneyDisabled] = useState(false);
const formSchema = sdmInfoFormSchema;
const [isLogin, setIsLogin] = useState(false);
const [facilityName, setFacilityName] = useState<string>('');
const [defaultValues, setDefaultValues] = useState<FormData>({
sdmNm: '',
const [facilityName, setFacilityName] = useState<string>("");
const [defaultValues, setDefaultValues] = useState<SdmInfoFormData>({
sdmNm: "",
demandYmd: new Date(),
demandQnty: '',
usePurpose: '',
money: '',
demandQnty: "",
usePurpose: "",
money: "",
/**요구수질 */
reqWaterQlty: '',
grssClfg: '',
tn: '',
combResdch: '',
tph: '',
trbt: '',
ph: '',
bod: '',
reqWaterQlty: "",
grssClfg: null,
tn: null,
combResdch: null,
tph: null,
trbt: null,
ph: null,
bod: null,
/**컬럼확인필요 */
clIonQnt: '',
smll: '',
cdtnv: '',
crmty: '',
rmrk: '',
clIonQnt: null,
smll: "",
cdtnv: null,
crmty: null,
rmrk: "",
/** 후보지 제목 */
candidateTitle: [
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' },
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' },
{ ruseUtztnFcltNm: '', ruseUtztnFcltCd: '' },
{ ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
{ ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
{ ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" },
],
});
useEffect(() => {
const user = sessionStorage.getItem('user');
setDefaultValues(prev => ({
const user = sessionStorage.getItem("user");
setDefaultValues((prev) => ({
...prev,
sdmNm: searchParams.get('dmdCoNm') || '',
sdmNm: searchParams.get("dmdCoNm") || "",
}));
sessionStorage.setItem('user', searchParams.get('dmdInfoId') || '');
setIsLogin(user || searchParams.get('dmdInfoId') ? true : false);
sessionStorage.setItem("user", searchParams.get("dmdInfoId") || "");
setIsLogin(user || searchParams.get("dmdInfoId") ? true : false);
}, [searchParams]);
useEffect(() => {
const local = localStorage.getItem('user');
const local = localStorage.getItem("user");
if (local) {
setIsLogin(true);
}
setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만)
}, [setSubsInfo]);
const form = useForm<FormData>({
const form = useForm<SdmInfoFormData>({
resolver: zodResolver(formSchema),
defaultValues,
mode: "onChange",
mode: "onSubmit",
});
const subsInfo = useDmStore((state) => state.map.subsInfo);
useEffect(()=>{
useEffect(() => {
//map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함
if(subsInfo.length>0){
form.setValue('candidateTitle', subsInfo)
if (subsInfo.length > 0) {
form.setValue("candidateTitle", subsInfo);
}
}, [subsInfo, form]);
},[subsInfo, form])
//후보지 팝업 값 셋팅
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === "SET_SUBS_INFO") {
setSubsInfo(event.data.payload); // zustand로 값 설정
}
};
//후보지 팝업 값 셋팅
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === 'SET_SUBS_INFO') {
setSubsInfo(event.data.payload); // zustand로 값 설정
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [setSubsInfo]);
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [setSubsInfo]);
// 로그인 성공 시 모달 닫기 및 폼 업데이트
useEffect(() => {
if (successLogin) {
setIsLogin(true);
const user = sessionStorage.getItem('user');
const user = sessionStorage.getItem("user");
if (user) {
try {
const userData = JSON.parse(user);
if (userData.dmdCoNm) {
setFacilityName(userData.dmdCoNm);
form.setValue('sdmNm', userData.dmdCoNm);
form.setValue("sdmNm", userData.dmdCoNm);
}
} catch (error) {
console.error('Error parsing user data:', error);
console.error("Error parsing user data:", error);
}
}
}
}, [successLogin, form]);
useEffect(() => {
console.log("Form state changed:", form.formState.errors);
}, [form.formState.errors]);
// 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화
useEffect(() => {
if (ismoneyDisabled) {
form.clearErrors('money');
form.setValue('money', '');
form.clearErrors("money");
form.setValue("money", "");
}
}, [ismoneyDisabled, form]);
const router = useRouter();
const onSubmit: SubmitHandler<FormData> = async (data) => {
const router = useRouter();
const onSubmit: SubmitHandler<SdmInfoFormData> = async (data) => {
try {
// Transform form data to match API schema
const apiData: DemandCompType = {
dmdInfoId: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''),
dmdInfoFk: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdInfoId?.toString() || searchParams.get('dmdInfoId') + (searchParams.get('bznmTotNo') || ''),
dmdCoNm: JSON.parse(sessionStorage.getItem('user') || '{}')?.dmdCoNm?.toString() || data.sdmNm,
ctpvCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.ctpvCd?.toString() || searchParams.get('ctpvCd') || '',
sggCd: JSON.parse(sessionStorage.getItem('user') || '{}')?.sggCd?.toString() || searchParams.get('sggCd') || '',
daddr: JSON.parse(sessionStorage.getItem('user') || '{}')?.daddr?.toString() || searchParams.get('daddr') || '',
telno: JSON.parse(sessionStorage.getItem('user') || '{}')?.telno?.toString() || searchParams.get('telno') || '',
rgtr: JSON.parse(sessionStorage.getItem('user') || '{}')?.rgtr?.toString() || searchParams.get('rgtr') || '',
useUsgCd: data.usePurpose || '',
wntUntprc: Number(data.money) || '',
const dmSubsInfo = data.candidateTitle?.filter(
(item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd
);
// Transform form data to madata.candidateTitletch API schema
const apiData: DemandCompBizType = {
dmdInfoId:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.dmdInfoId?.toString() ||
searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""),
dmdInfoFk:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.dmdInfoId?.toString() ||
searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""),
dmdCoNm:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.dmdCoNm?.toString() || data.sdmNm,
ctpvCd:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.ctpvCd?.toString() ||
searchParams.get("ctpvCd") ||
"",
sggCd:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.sggCd?.toString() ||
searchParams.get("sggCd") ||
"",
daddr:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.daddr?.toString() ||
searchParams.get("daddr") ||
"",
telno:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.telno?.toString() ||
searchParams.get("telno") ||
"",
rgtr:
JSON.parse(
sessionStorage.getItem("user") || "{}"
)?.rgtr?.toString() ||
searchParams.get("rgtr") ||
"",
useUsgCd: data.usePurpose || "",
wntUntprc: Number(data.money) || "",
dmndTmng: data.demandYmd || new Date(),
dmndQty: Number(data.demandQnty) || '',
grssClfg: Number(data.grssClfg) || '',
tn: Number(data.tn) || '',
combResdch: Number(data.combResdch) || '',
tph: Number(data.tph) || '',
trbt: Number(data.trbt) || '',
ph: Number(data.ph) || '',
blgcOxygDmndQnt: Number(data.bod) || '',
clIonQnt: Number(data.clIonQnt) || '',
smll: Number(data.smll) || '',
cdtnv: Number(data.cdtnv) || '',
crmty: Number(data.crmty) || '',
rmrk: data.rmrk || '',
dmndQty: Number(data.demandQnty) || "",
grssClfg: data.grssClfg === null ? -1 : Number(data.grssClfg),
tn: Number(data.tn) || null,
combResdch: Number(data.combResdch) || null,
tph: Number(data.tph) || null,
trbt: Number(data.trbt) || null,
ph: data.ph || "",
blgcOxygDmndQnt: Number(data.bod) ||null,
clIonQnt: Number(data.clIonQnt) || null,
smll: data.smll || "",
cdtnv: Number(data.cdtnv) ||null,
crmty: Number(data.crmty) || null,
rmrk: data.rmrk || "",
bzmnNo: "",
dmdAplyPswd: "",
index: 0,
reqDt: "",
dmdInfoSn: 0,
status: '',
splyCndtList: data.candidateTitle || [],
status: "",
splyCndtList: dmSubsInfo || [],
};
await insertSdmInfoData(apiData);
setTimeout(() => {
router.push("/");
}, 1000);
}, 1000); // 0.8초 후 이동
} catch (error) {
console.error('Error submitting form:', error);
console.error("Error submitting form:", error);
}
};
@@ -254,8 +232,10 @@ const router = useRouter();
/>
<div className="flex justify-center pt-4">
<Button
color="sky"
size={"sm"}
type="submit"
className="w-40 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200"
className="w-40 font-semibold py-2 px-4 rounded-md "
>
수요정보 등록
</Button>
@@ -265,20 +245,21 @@ const router = useRouter();
</div>
</div>
{!isLogin ?
{!isLogin ? (
<UiModal
body={<SdmLogin />}
bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]"
open={!isLogin}
onOpenChange={(open) => {
if (!open) {
toast.dismiss();
router.push("/");
}
}}
noHeaderYn="Y"
noFooterYn="Y"
/>
: null}
) : null}
<Toaster position="top-center" />
</div>
);
@@ -291,4 +272,3 @@ export default function MyForm() {
</Suspense>
);
}
@@ -1,8 +1,8 @@
"use client";
import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng";
import SdmInsertForm from '@common/business_components/ui/sdmInsertForm';
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useRef } from "react";
import { Toaster } from "react-hot-toast";
@@ -44,15 +44,9 @@ export default function MyForm() {
const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => {
const convertedData: DemandCompType = {
...data,
dmdInfoSn: '',
dmdInfoFk: '',
status: '',
splyCndtList: []
};
formDataRef.current = convertedData;
insertSdmData(convertedData).catch(error => {
console.error('Error inserting SDM data:', error);
});
insertSdmData(convertedData)
}, [insertSdmData]);
return (
@@ -70,4 +64,3 @@ export default function MyForm() {
</div>
);
}
@@ -131,7 +131,7 @@ export default function WrAboutPage() {
물재이용 필요성
</h2>
<section aria-labelledby="card2-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
<h1 id="card2-desc" className="font-NotoSansKR text-[#ED5534] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1>
<h1 id="card2-desc" className="font-NotoSansKR text-[#D63F1F] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1>
<p className="font-NotoSansKR text-2xl font-normal mb-10 leading-relaxed">
빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.<br />
물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서<br />가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다.
@@ -237,7 +237,7 @@ export default function WrAboutPage() {
하수 재이용
</h2>
<section aria-labelledby="card5-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
<h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#0099A4] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1>
<h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#00818A] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1>
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">사용하고 버려져 공공하수처리장으로 모여든 물은<br />용도에 맞는 처리 후 재이용 할 수 있습니다.</p>
<Image
src="/img/wrAbout/img_05_1.png"
+14 -10
View File
@@ -62,10 +62,9 @@ const Map = () => {
Object.keys(item).length > 0 &&
!prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId)
);
console.log(newItems)
console.log(newItems);
return [...prev, ...newItems];
});
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
@@ -232,16 +231,9 @@ const Map = () => {
return (
<>
<SearchBar
setHasuPk={(hasuPk: any) => {
if (hasuPk) {
setSelectDataPk(hasuPk);
}
}}
onApply={onApply}
/>
<div className="bg-container">
<Gis
height='h-screen'
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
@@ -257,6 +249,18 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={onCheckedLayersChange}
imgVisble={false}
searchBar={(props: any) => (
<SearchBar
setHasuPk={(hasuPk: any) => {
if (hasuPk) {
setSelectDataPk(hasuPk);
}
}}
onApply={onApply}
{...props}
/>
)}
waterDepth={undefined}
/>
</div>
<SelectedFcltList
+3
View File
@@ -8,6 +8,9 @@ export default function RootLayout({
}>) {
return (
<html lang="ko">
<head>
<title>물재이용 대민</title>
</head>
<body>
{children}
</body>
+262 -23
View File
@@ -2,12 +2,108 @@
import type { SelectDataType } from "@common/business_components/ui";
import type { TableUpperProps } from "../common";
import toast from "react-hot-toast";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { z } from "zod";
/**====================================
* Store - 타입
=====================================*/
export const sdmInfoFormSchema = z.object({
sdmNm: z.string().min(1, "수요처를 선택해주세요"),
demandYmd: z.date().optional(),
demandQnty: z
.string()
.min(1, "요구수량을 입력해주세요")
.regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"),
usePurpose: z.string().optional(),
money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"),
/**요구수질 */
reqWaterQlty: z.string().optional(),
grssClfg: z.number().nullish(),
tn: z.number().nullish(),
combResdch: z.number().nullish(),
tph: z.number().nullish(),
trbt: z.number().nullish(),
ph: z.string().nullish(),
bod: z.number().nullish(),
/**컬럼확인필요 */
clIonQnt: z.number().nullish(),
smll: z.string(),
cdtnv: z.number().nullish(),
crmty: z.number().nullish(),
rmrk: z.string().optional(),
/* telno: z.string()
.min(8, "전화번호는 8자 이상 입력해주세요")
.max(12, "전화번호는 12자 이내로 입력해주세요")
.regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */
candidateTitle: z
.array(
z.object({
ruseUtztnFcltNm: z.string(),
ruseUtztnFcltCd: z.string(),
})
)
.optional(),
}).superRefine((data, ctx) => {
const disabledFields = disableRules[data.usePurpose ?? ""] || [];
// 모든 수질 관련 필드
// 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함.
const allFields: (keyof typeof data)[] = [
"tn", "combResdch", "tph",
"trbt", "ph", "bod", "clIonQnt",
"smll", "cdtnv", "crmty",
];
const allFieldsName: Record<string, string> = {
tn: "총질소(T-N)(mg/L)",
combResdch: "결합잔류염소(mg/L)",
tph: "총인(T-P)(mg/L)",
trbt: "탁도(NTU)",
ph: "수소이온농도(pH)",
bod: "생물학적 산소요구량(BOD)(mg/L)",
clIonQnt: "염화물(mgCL/L)",
smll: "냄새",
cdtnv: "전기전도도(㎲/cm)",
crmty: "색도(도)",
};
allFields.forEach((field) => {
const isDisabled = disabledFields.includes(field);
if (!isDisabled) {
// disabled가 아닌 필드는 반드시 값이 있어야 함
const value = data[field];
if (typeof value === "string") {
if (value.trim().length === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [field],
message: `${allFieldsName[field]} 을 입력해주세요.`,
});
}
} else if (typeof value === "number") {
if (value <= 0 || Number.isNaN(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [field],
message: `${allFieldsName[field]} 을 입력해주세요.`,
});
}
} else if (value == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [field],
message: `${allFieldsName[field]} 을 입력해주세요.`,
});
}
}
});
});
export type SdmInfoFormData = z.infer<typeof sdmInfoFormSchema>;
// 수요처 정보
export type DemandCompType = {
export type DemandCompBizType = {
index : number | '';
dmdInfoId : string | '';
dmdInfoFk : string | '';
@@ -23,22 +119,23 @@ export type DemandCompType = {
wntUntprc : number | '';
dmndTmng : Date | '';
dmndQty : number | '';
grssClfg : number | '';
tn : number | '';
combResdch : number | '';
tph : number | '';
trbt : number | '';
ph : number | '';
blgcOxygDmndQnt : number | '';
clIonQnt : number | '';
smll : number | '';
cdtnv : number | '';
crmty : number | '';
grssClfg : number | null //총대장균군수
tn : number | null //총질소
combResdch : number | null //결합잔류염소
tph : number | null //총인
trbt : number | null //탁도
ph : string | ''; //수소이온농
blgcOxygDmndQnt : number | null //생물학적산소요구량
clIonQnt : number | null //염화물
smll : string | ''; //냄새
cdtnv : number | null; //전기전도도
crmty : number | null //색도
rmrk : string | '';
reqDt : string | '';
dmdInfoSn : number | '';
status : string | '';
splyCndtList:SupplyCandidate[]|[],
ruseUtztnFcltNm?:string|''
}
@@ -152,7 +249,8 @@ export type MoneyDetailType = {
clclnBrdnCst : string | '';
clclnEcqrRt : string | '';
}
// 규칙 타입 선언
export type DisableRulesType = Record<string, string[]>;
/**====================================
* Store - 데이터
====================================*/
@@ -160,6 +258,139 @@ export type MoneyDetailType = {
const demandCompList : DemandCompType[] = [
]
const disableRules: DisableRulesType = {
"청소": ["tn", 'tph',"clIonQnt" ,'cdtnv'],
"화장실용수": ["tn", 'tph',"clIonQnt" ,'cdtnv'],
"세척": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'],
"살수": ["combResdch", "crmty",'tph','tn',"clIonQnt",'cdtnv'],
"조경": ["combResdch", "crmty",'tph','cdtnv'],
"농업_간접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'],
"농업_직접용수": ["combResdch", "crmty",'tn','tph','clIonQnt'],
"하천": ["combResdch", "trbt", "crmty" ,'clIonQnt','cdtnv'],
'공업':[]
};
const defaultRules: Record<string, object> = {
"청소": {
grssClfg: null,
combResdch:0.2,
trbt: 2,
bod: 5,
crmty: 20,
ph: "5.8~8.5",
smll:'불쾌하지않을것',
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"화장실용수": {
grssClfg: null,
combResdch:0.2,
trbt: 2,
bod: 5,
crmty: 20,
smll:'불쾌하지않을것',
ph: "5.8~8.5",
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"세척": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
combResdch:null,
smll:'불쾌하지않을것',
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"살수": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
smll:'불쾌하지않을것',
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
cdtnv: null,
},
"조경": {
grssClfg: null,
trbt: 2,
bod: 5,
ph: "5.8~8.5",
smll:'불쾌하지않을것',
clIonQnt:250,
combResdch:null,
crmty:null,
tn:null,
tph: null,
cdtnv: null,
},
"농업_간접용수": {
grssClfg: null,
trbt: 5,
bod: 8,
ph: "5.8~8.5",
cdtnv:2000,
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
},
"농업_직접용수": {
grssClfg: null,
trbt: 2,
bod: 8,
smll:'불쾌하지않을것',
ph: "5.8~8.5",
cdtnv:700,
combResdch:null,
crmty:null,
tn:null,
tph: null,
clIonQnt: null,
},
"하천": {
grssClfg: 1000,
bod: 5,
tn: 10,
tph: 0.5,
smll:'불쾌하지않을것',
ph: "5.8~8.5",
combResdch:null,
trbt:null,
crmty:null,
clIonQnt: null,
cdtnv:null
},
'공업':{
grssClfg: null,
bod: null,
tn: null,
tph: null,
ph: "~",
combResdch:null,
smll:'불쾌하지않을것',
trbt:null,
crmty:null,
clIonQnt: null,
cdtnv:null
}
};
// 사용용도
const useTypesData : SelectDataType[] = [
{ text: '청소', val: "청소" },
@@ -167,9 +398,9 @@ const useTypesData : SelectDataType[] = [
{ text: "세척", val: "세척" },
{ text: "살수", val: "살수" },
{ text: "조경", val: "조경" },
{ text: "농업", val: "농업" },
{ text: "농업_간접용수", val: "농업_간접용수" },
{ text: "농업_직접용수", val: "농업_직접용수" },
{ text: "하천", val: "하천" },
{ text: "지하수", val: "지하수" },
{ text: "공업", val: "공업" },
];
@@ -280,7 +511,7 @@ const postSdmData = async (data: DemandCompType) => {
}
// 수요정보 등록
const postSdmInfoData = async (data: DemandCompType) => {
const postSdmInfoData = async (data: DemandCompBizType) => {
try {
await postFetch("/reuseFc/request/demandInfo", data);
toast.success('수요 정보 등록에 성공하였습니다.');
@@ -360,7 +591,7 @@ const fetchGetSimilarCaseList = async (part: string, value: number|string) => {
export type SdmActions = {
initializeSdmData: () => void;
insertSdmData: (data: DemandCompType) => Promise<void>;
insertSdmInfoData: (data: DemandCompType) => Promise<void>;
insertSdmInfoData: (data: DemandCompBizType) => Promise<void>;
getDemandCompList: (searchText: string, pageCnt: number) => Promise<void>;
actionLogin: (data: DemandCompType) => Promise<void>;
getSdmInfoStatusList: (dmdInfoId: string) => Promise<void>;
@@ -378,9 +609,10 @@ export type SdmType ={
moneyDetailList: MoneyDetailType[];
// 수요정보 신청현황
sdmInfoStatusList: DemandCompType[];
sdmInfoStatusList: DemandCompBizType[];
upHeadSdmInfoStatus: TableUpperProps[];
defaultRules: Record<string, object>
disableRules: DisableRulesType;
// 로그인관련
successNextPage : boolean;
successLogin: boolean;
@@ -398,7 +630,10 @@ export const SdmState: SdmType = {
moneyDetailList: moneyDetailList,
// 수요정보 신청현황
sdmInfoStatusList: [],
defaultRules:defaultRules,
disableRules:disableRules,
upHeadSdmInfoStatus: upHeadSdmInfoStatus,
// 로그인관련
successNextPage : false,
successLogin: false,
@@ -428,7 +663,7 @@ export const SdmReducer = (set: any): SdmActions => {
}
},
insertSdmInfoData: async (data: DemandCompType) => {
insertSdmInfoData: async (data: DemandCompBizType) => {
postSdmInfoData(data);
},
@@ -465,11 +700,13 @@ export const SdmReducer = (set: any): SdmActions => {
}))
return;
}
const sdmInfoStatusList : DemandCompType[] = [];
const sdmInfoStatusList : DemandCompBizType[] = [];
res.demandInfo.map((el: DemandCompType, index: number) => {
res.demandInfo.map((el: DemandCompBizType, index: number) => {
const matchingInfo = res.demandMatchings.find((matching: DemandMatchings) => matching.dmdInfoSn === el.dmdInfoSn);
const status = matchingInfo?.splyFcltFlg?.trim() === 'Sub' ? '검토' : matchingInfo?.splyFcltFlg?.trim() === 'matched' ? '매칭완료' : '신청';
const ruseUtztnFcltNm = matchingInfo?.ruseUtztnFcltNm ? matchingInfo.ruseUtztnFcltNm : '';
const tmpData = {
...el,
@@ -485,7 +722,9 @@ export const SdmReducer = (set: any): SdmActions => {
useUsgCd: el.useUsgCd,
dmndTmng: el.dmndTmng,
dmdInfoSn : el.dmdInfoSn,
status : status
status : status,
splyCndtList: el.splyCndtList,
ruseUtztnFcltNm: ruseUtztnFcltNm || ''
}
sdmInfoStatusList.push(tmpData);
})
@@ -1,5 +1,5 @@
import { useDmStore } from '@/app/store';
import type { DemandCompType } from '@/app/store/sdmMng';
import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form';
import React, { useCallback, useEffect, useRef, useState } from 'react';
type thisProps = {
@@ -0,0 +1,18 @@
export const sanitizeInput = (val: string, allowDecimal = false) => {
if (val === "") return ""; // 빈 값 허용
// 소숫점 허용
if (allowDecimal) {
// 숫자와 점만 허용
let cleaned = val.replace(/[^0-9.]/g, "");
// 소숫점 여러 개일 경우 첫 번째만 유지
const parts = cleaned.split(".");
if (parts.length > 2) {
cleaned = parts[0] + "." + parts.slice(1).join("");
}
return cleaned;
}
// 정수만 허용
return val.replace(/[^0-9]/g, "");
};
@@ -1,8 +1,6 @@
"use client";
import { useDmStore } from "@/app/store";
import {
SearchDiv,
SearchFormLeft,
SelectBox,
type SelectDataType,
} from "@common/business_components/ui";
@@ -48,6 +46,7 @@ export function SearchBar({
},
[Common.selectSigunData]
);
const onSelectValue = useCallback(
(val: string, type: string) => {
switch (type) {
@@ -62,78 +61,58 @@ export function SearchBar({
[initializeSigunData]
);
// useEffect(() => {
// console.log("map.ruseFcltList ", map.ruseFcltList);
// }, [map.ruseFcltList]);
return (
<>
<div className="grid grid-cols-4 gap-4 ">
<div className="col-span-4">
<SearchDiv>
<SearchFormLeft>
<div className="col-span-10 flex flex-row">
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl"
label="시도"
selectClass={""}
selectArray={Common.selectSidoData || []}
onSelectValue={(val) =>
onSelectValue(val.toString(), "ctpvCd")
}
value={sidoValue || Common.selectSidoData[0]?.val || ""}
/>
<SelectBox
className="col-span-2 w-1/6 h-full text-2xl ml-10"
label="시군구"
selectClass={""}
selectArray={filteredSigunData || []}
onSelectValue={(val) =>
onSelectValue(val.toString(), "sggCd")
}
value={sigunValue}
/>
<div className="col-span-6 flex flex-row items-center min-w-[27rem] ml-10">
<Label className="text-xl justify-self-center mt-2 mx-3">
하수재이용시설
</Label>
<SearchInputText
label={"시설명"}
initValue={undefined}
setValue={(searchNm?: string) => {
let sggOrCity = "";
if (searchNm) {
if (sigunValue === "0" || sigunValue === "") {
sggOrCity = sidoValue;
} else {
sggOrCity = sigunValue;
}
findRuseFclt(sggOrCity, searchNm);
}
}}
setRtnData={(rtnData: any) => {
setHasuPk(rtnData);
}}
searchList={map.ruseFcltList || []}
/>
</div>
<div className="flex flex-row space-x-2">
<Button
color="sky"
className="mt-2"
size={"sm"}
onClick={() => {
onApply();
}}
>
적용
</Button>
</div>
</div>
</SearchFormLeft>
</SearchDiv>
</div>
<div className="flex justify-center w-full px-4 py-3">
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
<SelectBox
labelClass="w-[40px]"
className="w-[200px]"
label="시도"
selectClass="rounded-lg bg-white"
selectArray={Common.selectSidoData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue || Common.selectSidoData[0]?.val || ""}
/>
<SelectBox
labelClass="w-[65px]"
className="w-[200px]"
label="시군구"
selectClass="rounded-lg bg-white"
selectArray={filteredSigunData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<Label className="text-gray-600">하수재이용시설</Label>
<SearchInputText
label="시설명"
initValue={undefined}
setValue={(searchNm?: string) => {
let sggOrCity = "";
if (searchNm) {
if (sigunValue === "0" || sigunValue === "") {
sggOrCity = sidoValue;
} else {
sggOrCity = sigunValue;
}
findRuseFclt(sggOrCity, searchNm);
}
}}
setRtnData={(rtnData: any) => {
setHasuPk(rtnData);
}}
searchList={map.ruseFcltList || []}
/>
<Button
color="sky"
className="ml-2 text-white"
size="sm"
onClick={() => {
onApply();
}}
>
적용
</Button>
</div>
</>
</div>
);
}
@@ -1,27 +1,29 @@
import { FormUi } from "@common/business_components/ui";
import type { ReactNode } from "react";
import type { ControllerRenderProps, FieldError, FieldValues, Path } from "react-hook-form";
import { cloneElement, isValidElement } from "react";
interface AccessibleFormFieldProps<T extends FieldValues> {
field: ControllerRenderProps<T, Path<T>>;
label: string;
required?: boolean;
error?: FieldError;
inputComponent: ReactNode;
isDisabled?: boolean;
inputComponent: React.ReactElement<{ disabled?: boolean }>;
}
export function AccessibleFormField<T extends FieldValues>({
label,
required = false,
inputComponent,
isDisabled = false,
}: AccessibleFormFieldProps<T>) {
const labelText = required ? `${label} *` : label;
return (
<FormUi label={labelText}>
<FormUi label={labelText} isDisabled={isDisabled}>
<div>
{inputComponent}
{isValidElement(inputComponent)
? cloneElement(inputComponent, { disabled: isDisabled } as Partial<typeof inputComponent.props>)
: inputComponent}
{/* {error?.message && Array.from(new Set(error.message.split('\n'))).map((msg, idx) => (
<p key={idx} className="text-red-500 text-sm mt-1" role="alert">
{msg}
@@ -1,5 +1,4 @@
import ModalSimilarPage from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page";
import type { FormData } from "@/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page";
import { useDmStore } from "@/app/store";
import {
CANDIDATE,
@@ -11,22 +10,30 @@ import {
UiModal,
} from "@common/business_components";
import { Input } from "@common/components";
import { Button } from "@common/components/ui";
import { Button, Label } from "@common/components/ui";
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { AccessibleFormField } from "./AccessibleFormField";
import type { ControllerRenderProps, UseFormReturn, FieldError } from "react-hook-form";
import {
type ControllerRenderProps,
type UseFormReturn,
type FieldError,
useWatch,
} from "react-hook-form";
import SearchInputText from "../common/searhDemandInput/component";
import type { DemandCompType } from "@/app/store/sdmMng";
import React from "react";
import { sanitizeInput } from "../common/utils/utils";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import type { SdmInfoFormData } from "@/app/store/sdmMng";
export interface SdmInfoRegFormProps {
form: UseFormReturn<FormData>;
form: UseFormReturn<SdmInfoFormData>;
onCheckboxChange?: (checked: boolean) => void;
facilityName?: string;
}
type FieldComponent = (
field: ControllerRenderProps<FormData, keyof FormData>
field: ControllerRenderProps<SdmInfoFormData, keyof SdmInfoFormData>
) => JSX.Element;
export function SdmInfoRegForm({
@@ -35,12 +42,15 @@ export function SdmInfoRegForm({
facilityName,
}: SdmInfoRegFormProps) {
const { initializeCommonData, Sdm } = useDmStore((state) => state);
const { useTypesData } = Sdm;
const { useTypesData, defaultRules, disableRules } = Sdm;
const searchParams = useSearchParams();
const [sdmNmValue, setSdmNmValue] = useState<string>(
searchParams.get("dmdCoNm") || ""
);
const [frontPHdata, setFrontPHdata] = useState("0");
const [backPHdata, setBackPHdata] = useState("0");
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false);
const [isSimilarCase, setIsSimilarCase] = useState(false);
const [key, setKey] = useState(0);
@@ -58,6 +68,7 @@ export function SdmInfoRegForm({
useEffect(() => {
console.log("candidateTitle ", form.getValues("candidateTitle"));
}, [form.getValues]);
useEffect(() => {
if (useTypesData && useTypesData.length > 0) {
setTimeout(() => {
@@ -109,6 +120,52 @@ export function SdmInfoRegForm({
onCheckboxChange?.(checked);
};
useEffect(() => {
const watchPH = form.getValues(`ph`);
if (watchPH && watchPH !== undefined) {
const [frontPH, backPH] = watchPH.split("~");
if (frontPH) {
setFrontPHdata(frontPH);
}
if (backPH) {
setBackPHdata(backPH);
}
} else {
setFrontPHdata("0");
setBackPHdata("0");
}
}, [form.getValues(`ph`)]);
const isSetPh = useCallback(
(typePHdata: string, index: number) => {
const PHdata = form.getValues("ph");
const isPH = PHdata?.split("~")[index] || "";
if (index == 0) {
form.setValue("ph", `${isPH}~${typePHdata}`);
setBackPHdata(typePHdata);
} else if (index == 1) {
form.setValue("ph", `${typePHdata}~${isPH}`);
setFrontPHdata(typePHdata);
}
},
[form]
);
const usePurpose = useWatch({ control: form.control, name: "usePurpose" });
useEffect(() => {
if (!usePurpose) return;
// Apply default values
const defaults = defaultRules[usePurpose];
if (defaults) {
Object.entries(defaults).forEach(([field, value]) => {
form.setValue(field as keyof SdmInfoFormData, value, { shouldDirty: true });
});
}
}, [form.getValues("usePurpose")]);
// 시설 선택시 이벤트
const selectComp = useCallback(
(rtnData: DemandCompType[]) => {
@@ -119,7 +176,7 @@ export function SdmInfoRegForm({
);
const formFields: Array<{
name: keyof FormData;
name: keyof SdmInfoFormData;
label: string;
component: FieldComponent | undefined;
formIndex: number;
@@ -178,7 +235,6 @@ export function SdmInfoRegForm({
required: true,
component: (field) => (
<Input
placeholder="요구수량(㎥/d)을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
@@ -222,7 +278,6 @@ export function SdmInfoRegForm({
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="희망단가(원/㎥)을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
@@ -244,221 +299,7 @@ export function SdmInfoRegForm({
required: true,
component: undefined,
},
{
name: "grssClfg",
label: "총대장균군수(개/100ml)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총대장균군수를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "tn",
label: "총질소(T-N)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총질소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "combResdch",
label: "결합잔류염소(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="결합잔류염소를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "tph",
label: "총인(T-P)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="총인을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "trbt",
label: "탁도(NTU)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="탁도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "ph",
label: "수소이온농도(pH)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="수소이온농도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "bod",
label: "생물학적 산소요구량(BOD)(mg/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="생물학적 산소요구량을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "clIonQnt",
label: "염화물(mgCL/L)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="염화물을 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "smll",
label: "냄새",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="냄새를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "cdtnv",
label: "전기전도도(㎲/cm)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="전기전도도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "crmty",
label: "색도(도)",
formIndex: 1,
required: true,
component: (field) => (
<Input
placeholder="색도를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "rmrk",
label: "비고",
formIndex: 1,
component: (field) => (
<Input
placeholder="비고를 입력하세요"
type="text"
className="!text-xl text-gray-600"
value={field.value as string}
onChange={field.onChange}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
),
},
{
name: "candidateTitle",
label: CANDIDATE || "공급처 후보지 선택(필수아님)",
@@ -466,10 +307,20 @@ export function SdmInfoRegForm({
component: () => (
<div>
<div className="-mt-14 float-right flex gap-10">
<Button className={"bg-gray-200"} onClick={searchCandidate}>
<Button
className="rounded-md"
color="sky"
size={"sm"}
onClick={searchCandidate}
>
후보지 조회
</Button>
<Button className={"bg-gray-200"} onClick={searchSimilarCase}>
<Button
className="rounded-md"
color="sky"
size={"sm"}
onClick={searchSimilarCase}
>
유사사례
</Button>
</div>
@@ -495,9 +346,7 @@ export function SdmInfoRegForm({
const normalFields = formFields.filter(
(f) => f.formIndex === 0 && f.component
);
const groupFields = formFields.filter(
(f) => f.formIndex === 1 && f.component
);
const candidateFields = formFields.filter(
(f) => f.formIndex === 2 && f.component
);
@@ -512,17 +361,20 @@ export function SdmInfoRegForm({
name={name}
render={({ field }) =>
name === "money" ? (
component(field)
<div>
<Label className="text-black" >
희망단가*
</Label>
{ component(field)}
</div>
// <FormUi label={label + "*"}>{component(field)}</FormUi>
) : (
// <FormUi label={label}>
// {component(field)}
// </FormUi>
<AccessibleFormField
field={field}
label={label}
required={required}
error={
typeof form.formState.errors[name]?.message === 'string'
typeof form.formState.errors[name]?.message === "string"
? (form.formState.errors[name] as FieldError)
: undefined
}
@@ -534,37 +386,527 @@ export function SdmInfoRegForm({
) : null
)}
{groupFields.length > 0 && (
<div className="mt-10">
<FormLabel className="text-xl">요구수질</FormLabel>
<div className="flex flex-wrap justify-start gap-10 mt-3 mb-3">
{groupFields.map(({ name, label, component, required }) => (
<div key={name} className="min-w-[180px]">
<FormField
control={form.control}
name={name}
render={({ field }) => (
// <FormUi label={label}>
// {component?.(field)}
// </FormUi>
<AccessibleFormField
field={field}
label={label}
required={required}
error={
typeof form.formState.errors[name]?.message === 'string'
? (form.formState.errors[name] as FieldError)
: undefined
}
inputComponent={component?.(field)}
/>
)}
/>
</div>
))}
<div className="mt-10">
<FormLabel className="text-xl">요구수질</FormLabel>
<div className="grid grid-cols-2 gap-x-10 gap-y-6 mt-3 mb-3">
<FormField
control={form.control}
name={`grssClfg`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `총대장균군수(개/mg)*` : "총대장균군수(개/mg)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: "불검출"
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
{/* TODO: 총대장균군수 확인 필요*/}
{field.value !== null ? (
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
) : (
<></>
)}
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`tn`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `총질소(T-N)(mg/L)*` : "총질소(T-N)(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`combResdch`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `결합잔류염소(mg/L)*` : "결합잔류염소(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const cleanValue = sanitizeInput(
e.target.value,
true
);
field.onChange(
cleanValue === "" ? null : Number(cleanValue)
);
}}
value={field.value ?? ""}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이상
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`tph`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={!isDisabled ? `총인(T-P)(mg/L)*` : "총인(T-P)(mg/L)"}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const cleanValue = sanitizeInput(
e.target.value,
true
);
field.onChange(
cleanValue === "" ? null : Number(cleanValue)
);
}}
value={field.value ?? ""}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`trbt`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `탁도(NTU)*` : "탁도(NTU)"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<div className="col-span-1">
<Label className="text-xl">수소이온농도(PH)</Label>
<div className="flex flex-row items-center gap-2">
<Input
className="!text-xl w-36"
placeholder=""
type="text"
onChange={(e) => {
const cleanValue = sanitizeInput(e.target.value, true);
isSetPh(cleanValue, 1);
}}
value={frontPHdata ?? ""}
maxLength={5}
/>
<span className="text-xl mx-2">~</span>
<Input
className="!text-xl w-36 "
placeholder=""
type="text"
onChange={(e) => {
const cleanValue = sanitizeInput(e.target.value, true);
isSetPh(cleanValue, 0);
}}
value={backPHdata ?? ""}
maxLength={5}
/>
</div>
</div>
<FormField
control={form.control}
name={`bod`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled
? `생물학적 산소요구량(BOD)(mg/L)*`
: "생물학적 산소요구량(BOD)(mg/L)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`clIonQnt`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={!isDisabled ? `염화물(mgCL/L)*` : "염화물(mgCL/L)"}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`smll`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `냄새*` : "냄새"}>
<SelectBox
className="!text-2xl rounded-md"
label={""}
selectClass={undefined}
selectArray={[
{ text: "불쾌하지않을것", val: "불쾌하지않을것" },
{ text: "해당사항없음", val: "해당사항없음" },
]}
onSelectValue={(val: string | number) => {
field.onChange(val.toString());
}}
value={field.value }
readOnly={isDisabled}
onBlur={field.onBlur}
ref={field.ref}
/>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`cdtnv`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi
label={
!isDisabled ? `전기전도도(㎲/cm)*` : "전기전도도(㎲/cm)"
}
>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`crmty`}
render={({ field }) => {
const disabledFields = usePurpose ? disableRules[usePurpose] : [];
const isDisabled = disabledFields
? disabledFields.includes(field.name)
: true;
return (
<FormUi label={!isDisabled ? `색도(도)*` : "색도(도)"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(
/[^0-9]/g,
""
); // 정수만
field.onChange(
inputValue === "" ? null : Number(inputValue)
);
}}
value={
field.value !== undefined && field.value !== null
? `${field.value}`
: ""
}
maxLength={6}
disabled={isDisabled}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
이하
</span>
</div>
</div>
</FormUi>
);
}}
/>
<FormField
control={form.control}
name={`rmrk`}
render={({ field }) => {
return (
<FormUi label={"비고"}>
<div className="flex items-center gap-2">
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
placeholder=""
onChange={(e) => {
field.onChange(e.target.value);
}}
value={
field.value !== undefined && field.value !== ""
? `${field.value}`
: ""
}
onBlur={field.onBlur}
name={field.name}
ref={field.ref}
/>
</div>
</div>
</FormUi>
);
}}
/>
</div>
)}
</div>
{candidateFields.map(({ name, label, component, required }, index) =>
component ? (
@@ -581,7 +923,7 @@ export function SdmInfoRegForm({
label={label}
required={required}
error={
typeof form.formState.errors[name]?.message === 'string'
typeof form.formState.errors[name]?.message === "string"
? (form.formState.errors[name] as FieldError)
: undefined
}
@@ -1,11 +1,12 @@
import { useDmStore } from "@/app/store";
import type { DemandCompType } from "@/app/store/sdmMng";
import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import { Button, Input } from "@common/components";
import { zodResolver } from "@hookform/resolvers/zod";
import { sha512 } from 'js-sha512';
import { useEffect, useState, useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form";
import toast from "react-hot-toast";
import * as z from "zod";
export function SdmLoginForm() {
@@ -15,10 +16,10 @@ export function SdmLoginForm() {
const formatBusinessNumber = (value: string) => {
// 숫자만 추출
const digitsOnly = value.replace(/\D/g, "");
// 10자리까지만 처리
const limitedDigits = digitsOnly.slice(0, 10);
// 3-2-5 형식으로 포맷팅
if (limitedDigits.length <= 3) {
return limitedDigits;
@@ -28,33 +29,33 @@ export function SdmLoginForm() {
return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`;
}
};
// 스키마를 useMemo로 최적화
const sdmLoginFormSchema = useMemo(() => z.object({
dmdInfoId: z.string()
.min(1, "사업자등록번호를 입력해주세요")
.regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"),
bznmTotNo: isBznmTotNoDisabled
bznmTotNo: isBznmTotNoDisabled
? z.string().optional()
: z.string().length(4, "종사업자등록번호는 4자리 숫자만 입력 가능합니다"),
password: z.string()
.min(1, "비밀번호를 입력해주세요")
.min(8, "비밀번호는 8자 이상 입력해주세요")
.min(8, "비밀번호는 8자 이상 입력해주세요")
.max(16, "비밀번호는 16자 이내로 입력해주세요"),
rgtr: z.string().optional(),
dmdAplyPswd: z.string().optional(),
}), [isBznmTotNoDisabled]);
type FormData = z.infer<typeof sdmLoginFormSchema>;
type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element;
type SdmLoginForm = z.infer<typeof sdmLoginFormSchema>;
type FieldComponent = (field: ControllerRenderProps<SdmLoginForm, keyof SdmLoginForm>) => JSX.Element;
const formSchema = sdmLoginFormSchema;
useEffect(() => {
initializeCommonData();
}, [initializeCommonData]);
const defaultValues: FormData = {
const defaultValues: SdmLoginForm = {
dmdInfoId: '',
bznmTotNo: '',
password: '',
@@ -62,7 +63,7 @@ export function SdmLoginForm() {
dmdAplyPswd: '',
};
const form = useForm<FormData>({
const form = useForm<SdmLoginForm>({
resolver: zodResolver(formSchema),
defaultValues,
mode: "onChange",
@@ -72,6 +73,26 @@ export function SdmLoginForm() {
setIsBznmTotNoDisabled(checked);
};
const initPassword = () => {
toast(
(t) => (
<div className="flex flex-col items-center">
<div className="text-2xl mb-2">
관리자(031-0000-0000)에게 비밀번호 초기화 요청을 유선으로 하시기 바랍니다.
</div>
<Button size={"sm"} onClick={() => toast.dismiss(t.id)}>
확인
</Button>
</div>
),
{
id: "pwd-reset",
duration: Infinity,
position: "top-center",
}
);
}
const handleLogin = () => {
const data = form.getValues();
const passwordStr = sha512.hex(data.password)
@@ -82,61 +103,61 @@ export function SdmLoginForm() {
}
actionLogin(param as unknown as DemandCompType);
};
const formFields: Array<{
name: keyof FormData;
name: keyof SdmLoginForm;
label: string;
component: FieldComponent | undefined;
}> = [
{
name: "dmdInfoId",
label: "사업자등록번호",
component: (field) => (
<Input
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!mt-5 !mb-5 !text-xl text-gray-600"
{...field}
value={formatBusinessNumber(field.value as string)}
onChange={(e) => {
const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10);
field.onChange(dmdId);
}}
/>
)
},
{
name: "bznmTotNo",
label: BZNM_TOT_NO || "종사업자등록번호",
component: (field) => (
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
{
name: "dmdInfoId",
label: "사업자등록번호",
component: (field) => (
<Input
placeholder="사업자등록번호를 입력하세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
className="!mt-5 !mb-5 !text-xl text-gray-600"
{...field}
value={formatBusinessNumber(field.value as string)}
onChange={(e) => {
const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10);
field.onChange(dmdId);
}}
/>
)
},
{
name: "bznmTotNo",
label: BZNM_TOT_NO || "종사업자등록번호",
component: (field) => (
<FormUi
label="종사업자등록번호"
onCheckboxChange={handleCheckboxChange}
isDisabled={isBznmTotNoDisabled}
>
<Input
placeholder="여러사업장이 있을 경우 입력해주세요"
type="text"
className="!mt-5 !text-xl text-gray-600"
disabled={isBznmTotNoDisabled}
{...field}
/>
</FormUi>
)
},
{
name: "password",
label: "비밀번호",
component: (field) => (
<Input
placeholder="비밀번호를 입력하세요"
type="password"
className="!mt-5 !mb-5 !text-xl text-gray-600"
{...field}
/>
</FormUi>
)
},
{
name: "password",
label: "비밀번호",
component: (field) => (
<Input
placeholder="비밀번호를 입력하세요"
type="password"
className="!mt-5 !mb-5 !text-xl text-gray-600"
{...field}
/>
)
}
];
)
}
];
return (
<>
@@ -148,11 +169,11 @@ export function SdmLoginForm() {
control={form.control}
name={name}
render={({ field }) => (
label==='종사업자등록번호' ?
<>
label === '종사업자등록번호' ?
<>
{component && component(field)}
</>
: <FormUi label={label}>
: <FormUi label={label}>
{component && component(field)}
</FormUi>
)}
@@ -160,12 +181,15 @@ export function SdmLoginForm() {
))}
</FormProvider>
<div className="flex justify-center pt-4">
<Button
onClick={handleLogin}
className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200"
>
로그인
</Button>
<div className="flex flex-col items-center">
<Button
onClick={handleLogin}
className="w-32 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition-colors duration-200"
>
로그인
</Button>
<div className="text-xl mt-2 text-gray-400 cursor-pointer" onClick={initPassword}>비밀번호 초기화 요청</div>
</div>
</div>
</div>
</>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 35 KiB

@@ -16,7 +16,6 @@ export class LoginApiService implements LoginService {
public async login(payload: ILogin): Promise<void> {
try {
console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN)
const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, {
method: "POST",
body: JSON.stringify({
+1
View File
@@ -2,6 +2,7 @@ NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 11300
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
+2 -1
View File
@@ -1,7 +1,8 @@
NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 3000
NEXT_PUBLIC_PROXY_HOST = http://localhost:3000
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
+1
View File
@@ -15,6 +15,7 @@ NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 11300
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
@@ -169,10 +169,9 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}:
} else if (index === 8) {
el['item'] = (Number(searchYear)-0).toString();
el['provisionalApproval'] = waterUsage1;
} else if(index === 4){
el['provisionalApproval'] = Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100;
}
});
console.log('planFcltyList :', planFcltyList);
}
}, [waterUsage1, waterUsage2, waterUsage3, searchYear, planFcltyList]);
// 연도가 바뀌면 hasSearched를 false로 리셋
@@ -226,8 +225,8 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}:
:(index < 5 || index > 5)&& (index!= 4) && (head.id ==='provisionalApproval'||head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour')
?<TableCell className="text-right" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:(head.id === "provisionalApproval" && index === 4)
?<TableCell colSpan={6} className="text-right border border-[var(--border)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') && index === 4)
?<TableCell colSpan={6} className="text-right border border-[var(--border)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{Math.round((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3 * 100)/100}</TableCell>
:((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') )
?null
:(head.id === 'item' && index != 5)?
<TableCell className="text-center text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
+22 -15
View File
@@ -3,6 +3,7 @@
import Gis from "@common/business_components/ui/map/Gis";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { useEffect, useRef, useState } from "react";
import { SearchBar } from "./searchBar/searchBar";
interface FeatureData {
fid?: string;
@@ -13,7 +14,11 @@ interface FeatureData {
}
interface MapInstance {
getLayers: () => { getArray: () => Array<{ getSource?: () => { getFeatureById?: (id: string) => FeatureData | null } }> };
getLayers: () => {
getArray: () => Array<{
getSource?: () => { getFeatureById?: (id: string) => FeatureData | null };
}>;
};
getView: () => { fit: (extent: number[], options: object) => void };
}
@@ -26,20 +31,20 @@ const Map = () => {
const fetchData = async () => {
try {
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`;
const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`;
const response = await fetch(reqUrl, {
method: "GET",
headers: {
accept: "application/json",
}
},
});
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
const data = await response.json(); // 여기서 json 파싱
const data = await response.json(); // 여기서 json 파싱
setTreeData(data); // 데이터 설정
} catch (error) {
console.error("데이터 불러오기 실패:", error);
@@ -60,7 +65,12 @@ const Map = () => {
}
// 항상 같은 select 객체 사용
const select = (GisApp.Module.select as unknown) as { resetSelectedFeature: () => void; selectFeature: (feature: FeatureData, selected: boolean) => void } | undefined;
const select = GisApp.Module.select as unknown as
| {
resetSelectedFeature: () => void;
selectFeature: (feature: FeatureData, selected: boolean) => void;
}
| undefined;
if (!select) return;
// 1. 기존 선택 초기화
@@ -114,21 +124,15 @@ const Map = () => {
setIsSearchModalOpen(false);
};
// onCheckedLayersChange handler
const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: unknown[]) => {
// Handle checked layers change if needed
console.log('Checked layers changed:', checkedIds, checkedNodesData);
};
return (
<div className="bg-container">
<Gis
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
isMiniMap={true}
searchView={null}
detailView={null}
isMiniMap={false}
searchView={undefined}
detailView={undefined}
onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
@@ -136,7 +140,10 @@ const Map = () => {
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
onCheckedLayersChange={undefined}
imgVisble={false}
searchBar={() => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />}
waterDepth={undefined}
/>
</div>
);
@@ -0,0 +1,158 @@
"use client";
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
interface SearchBarProps {
onFeatureSelect: (feature: any) => void;
gisMaps: React.RefObject<any>;
}
export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
const [sidoValue, setSidoValue] = useState("0");
const [sigunValue, setSigunValue] = useState("0");
const [sidoData, setSidoData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [sigunData, setSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [facilityData, setFacilityData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
const [facilityValue, setFacilityValue] = useState("0");
const fetchSido = async () => {
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`;
const res = await fetch(url);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.mctpv_korn_nm,
val: f.properties.mctpv_cd,
}));
setSidoData([{ text: "선택", val: "0" }, ...list]);
};
const fetchSigun = async () => {
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`;
const res = await fetch(url);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.sgg_korn_nm,
val: f.properties.sgg_cd,
}));
setSigunData(list);
};
const fetchFacilitiesBySggNm = async (sggNm: string) => {
const encodedSggNm = encodeURIComponent(sggNm);
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`;
const res = await fetch(url);
const json = await res.json();
const list = json.features.map((f: any) => ({
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
val: f.id, // f.properties.swtrmt_fclt_cd 도 가능
}));
setFacilityData([{ text: "선택", val: "0" }, ...list]);
setFacilityValue("0");
};
useEffect(() => {
fetchSido();
fetchSigun();
}, []);
const initializeSigunData = useCallback(
(sidoCd: string) => {
if (sidoCd === "0") {
setFilteredSigunData([{ text: "선택", val: "0" }]);
setSigunValue("0");
return;
}
const prefix = sidoCd.substring(0, 2);
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]);
setSigunValue("0");
},
[sigunData]
);
const onSelectValue = useCallback(
(val: string, type: string) => {
if (type === "ctpvCd") {
setSidoValue(val);
initializeSigunData(val);
setFacilityData([{ text: "선택", val: "0" }]);
} else if (type === "sggCd") {
setSigunValue(val);
const sggNm = sigunData.find((item) => item.val === val)?.text || "";
if (sggNm) fetchFacilitiesBySggNm(sggNm);
} else if (type === "fcltCd") {
setFacilityValue(val);
}
},
[initializeSigunData, sigunData]
);
const handleSearch = () => {
if (!gisMaps.current || facilityValue === "0") return;
const layers = gisMaps.current.getLayers().getArray();
for (const layer of layers) {
const source = layer.getSource?.();
const feature = source?.getFeatureById?.(facilityValue);
if (feature) {
onFeatureSelect(feature);
return;
}
}
alert("해당 처리시설을 지도에서 찾을 수 없습니다.");
};
return (
<div className="grid grid-cols-4 gap-4">
<div className="col-span-4">
<div className="card card-border flex">
<div className="col-span-10 flex flex-row">
<SelectBox
labelClass="w-[40px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={sidoData}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue}
/>
<SelectBox
labelClass="w-[65px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<SelectBox
labelClass="w-[140px]"
className="mr-3 w-[250px]"
selectClass="rounded-lg bg-white"
label="하수처리시설"
selectArray={facilityData}
onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")}
value={facilityValue}
/>
</div>
<div className="flex flex-row space-x-2">
<Button
color="primary"
className="mx-2 mt-1 ml-10"
size={"sm"}
onClick={handleSearch}
>
조회
</Button>
</div>
</div>
</div>
</div>
);
}
+3 -3
View File
@@ -1232,9 +1232,9 @@ export const dashBoardReducer:(set:(fn: (state: DashBoardType) => DashBoardType)
}
if(operationalStatusList[0] && operationalStatusList[7]){
operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT) * 100) * 10) / 10+'%';
operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT) * 100) * 10) / 10+'%';
operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / Number(data.winter.AVG_INFL_AMNT) * 100) * 10) / 10+'%';
operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : data.yearData && data.yearData.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterAvg) / (data.yearData && data.yearData.AVG_INFL_AMNT ? Number(data.yearData.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%';
operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : data.summer && data.summer.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterSummer) / (data.summer && data.summer.AVG_INFL_AMNT ? Number(data.summer.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%';
operationalStatusList[7].groundWaterWinter = operationalStatusList[0].groundWaterWinter === null ? '' : data.winter && data.winter.AVG_INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].groundWaterWinter) / (data.winter && data.winter.AVG_INFL_AMNT ? Number(data.winter.AVG_INFL_AMNT) : 0) * 100) * 10) / 10+'%';
operationalStatusList[7].rdi = operationalStatusList[0].rdi === null ? '' : data.summerBod && data.summerBod.INFL_AMNT === 0 ? '' : Math.round((Number(operationalStatusList[0].rdi) / (data.summerBod&&data.summerBod.INFL_AMNT&&data.summerBod.INFL_AMNT!='null'?Number(data.summerBod.INFL_AMNT):0) * 100) * 10) / 10+'%';
/* operationalStatusList[7].groundWaterAvg = operationalStatusList[0].groundWaterAvg === null ? '' : Number(Number(operationalStatusList[0].groundWaterAvg) / Number(data.yearData.AVG_INFL_AMNT)) * 100;
operationalStatusList[7].groundWaterSummer = operationalStatusList[0].groundWaterSummer === null ? '' : Number(Number(operationalStatusList[0].groundWaterSummer) / Number(data.summer.AVG_INFL_AMNT)) * 100;
@@ -446,7 +446,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy
let maxWaterUsage = 0;
let minWaterUsage = 9999999999;
let avgWaterUsage = 0;
console.log('monthData : ', monthData);
monthData.forEach((item, index) => {
item.usege = item.sum/item.cnt
tableData.push({
@@ -465,8 +464,6 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy
});
avgWaterUsage = tot/cnt
console.log('avgWaterUsage : ', avgWaterUsage);
console.log('tableData : ', tableData);
const newData= [
{
msrDt: '평균',
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 923 B

@@ -17,8 +17,6 @@ export class LoginApiService implements LoginService {
public async login(payload: ILogin): Promise<void> {
try {
console.log(' ENDPOINT.USER_SERVICE.LOGIN ', ENDPOINT.USER_SERVICE.LOGIN)
console.log(payload.userId+' '+payload.password)
const response = await fetch( PROXY_HOST+ ENDPOINT.USER_SERVICE.LOGIN, {
method: "POST",
body: JSON.stringify({
+1 -1
View File
@@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함
NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token
+1 -1
View File
@@ -5,7 +5,7 @@ NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token
+1 -1
View File
@@ -9,5 +9,5 @@ NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000
NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082
NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api
+10
View File
@@ -1,10 +1,12 @@
"use client";
import React, { useEffect } from 'react';
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
import useStore from '../store/useStore';
export default function MapLayout({
children,
@@ -17,6 +19,14 @@ export default function MapLayout({
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore();
useEffect(() => {
fetchCtpvSggData();
fetchBIZ_SE_CD();
fetchBIZ_TY_CD();
}, []);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
@@ -1,25 +0,0 @@
import { create } from 'zustand';
export type SelectDataType = {
text: string;
val: string;
sido?: string; // 시도 코드 (선택 사항)
}
interface CityCdStore {
cityCd: string;
setCityCd: (cityCd: string) => void;
sidos: SelectDataType[];
setSidos: (sidos: SelectDataType[]) => void;
siguns : SelectDataType[];
setSiguns: (siguns: SelectDataType[]) => void;
}
export const useCityCdStore = create<CityCdStore>((set) => ({
cityCd: '',
setCityCd: (cityCd: string) => set({ cityCd }),
sidos: [],
setSidos: (sidos: SelectDataType[]) => set({ sidos }),
siguns: [],
setSiguns: (siguns: SelectDataType[]) => set({ siguns }),
}));
@@ -12,6 +12,9 @@ type LayerComponentProps = {
rainMeasureData?: any;
onSaveClick?: () => void;
onFormDataChange?: (data: any) => void; // 추가
chmbrXcnts: string;
chmbrYdnts: string;
gisObject?: Record<string, any>; // GIS 객체 추가
};
type LayerContentMap = {
[key: string]: {
@@ -22,7 +25,7 @@ type LayerContentMap = {
// 레이어 이름과 컴포넌트를 매핑
// 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업
const layerContentMap: LayerContentMap = {
// "하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리
"하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리
"하수처리구역": {},
"우수토실": { component: Ssc, },
"중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정
@@ -28,20 +28,20 @@ export const MsSwagePplCol = [
// { id: 'lyrTypeNm', title: '레이어타입명' },
]
export const MsSwagePplCol2 = [
{ id: 'rowNumber', title: '일련번호' },
{ id: 'fcltMngNo', title: '시설 관리번호' },
{ id: 'instMngNo', title: '기관 관리번호' },
{ id: 'rowNumber', title: '일련\n번호' },
{ id: 'fcltMngNo', title: '시설\n관리번호' },
{ id: 'instMngNo', title: '기관\n관리번호' },
{ id: 'instlYmd', title: '설치일자' },
// { id: 'mapshtNo', title: '도엽번호' },
{ id: 'pdmt', title: '관경(mm)' },
{ id: 'prlg', title: '연장(m)' },
{ id: 'avgGrdn', title: '평균구배(m/m)' },
{ id: 'tmptDpth', title: '시점깊이(m)' },
{ id: 'tmptMhlMngNo', title: '시점맨홀 관리번호' },
{ id: 'scendDpth', title: '종점깊이(m)' },
{ id: 'scendMhlMngNo', title: '종점맨홀 관리번호' },
{ id: 'vrtcLen', title: '세로길이(m)' },
{ id: 'wdthLen', title: '가로길이(m)' },
{ id: 'pdmt', title: '관경\n(mm)' },
{ id: 'prlg', title: '연장\n(m)' },
{ id: 'avgGrdn', title: '평균구배\n(m/m)' },
{ id: 'tmptDpth', title: '시점깊이\n(m)' },
{ id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' },
{ id: 'scendDpth', title: '종점깊이\n(m)' },
{ id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' },
{ id: 'vrtcLen', title: '세로길이\n(m)' },
{ id: 'wdthLen', title: '가로길이\n(m)' },
// { id: 'fcltyShpCd', title: '시설물형태코드' },
// { id: 'fid', title: 'FID' },
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
@@ -297,9 +297,9 @@ export const SwtrmtFcltCol = [
// { id: 'obsrvnSptCd', title: '관측지점코드' },
];
export const SwtrmtFcltCol2 = [
{ id: 'rowNumber', title: '일련번호' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
{ id: 'fcltCpct', title: '시설용량(㎥/d)' },
{ id: 'rowNumber', title: '일련\n번호' },
{ id: 'swtrmtFcltNm', title: '하수처리\n시설명' },
{ id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' },
{ id: 'rgnExpln', title: '지역설명' },
// { id: 'cmcnYmd', title: '준공일자' },
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
@@ -311,7 +311,7 @@ export const SwtrmtFcltCol2 = [
// { id: 'sggNm', title: '시군구명' },
// { id: 'sareaCd', title: '권역코드' },
{ id: 'sareaNm', title: '권역명' },
{ id: 'lrgeSareaNm', title: '대권역명' },
// { id: 'lrgeSareaNm', title: '대권역명' },
{ id: 'rvrNm', title: '하천명' },
// { id: 'addr', title: '주소' },
// { id: 'dtlAddr', title: '상세주소' },
@@ -320,19 +320,19 @@ export const SwtrmtFcltCol2 = [
{ id: 'dschgWtstNm', title: '방류수계명' },
// { id: 'pllnArea', title: '부지면적(m²)' },
// { id: 'mngRprtvBzentyNm', title: '관리대행업체명' },
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수(인)' },
{ id: 'swtrmtPpltnCnt', title: '하수\n처리\n인구수\n(인)' },
// { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' },
// { id: 'xcrd', title: 'X좌표' },
// { id: 'ycrd', title: 'Y좌표' },
// { id: 'oprtngSttsCd', title: '가동상태코드' },
{ id: 'rprsMsrcCn', title: '대표공법내용' },
{ id: 'rprsMsrcCn', title: '대표\n공법\n내용' },
// { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량(mg/L)' },
// { id: 'sttyCrtrToc', title: '법정기준TOC(mg/L)' },
// { id: 'sttyCrtrSs', title: '법정기준SS(mg/L)' },
// { id: 'sttyCrtrTn', title: '법정기준총질소(mg/L)' },
// { id: 'sttyCrtrTph', title: '법정기준총인(mg/L)' },
// { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수(군수/100mL)' },
{ id: 'bsnNm', title: '유역명' },
// { id: 'bsnNm', title: '유역명' },
// { id: 'obsrvnSptCd', title: '관측지점코드' },
];
@@ -353,9 +353,9 @@ export const SwtrmtPrcsZoneCol2 = [
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
// { id: 'ctpv_cd', title: '시도코드' },
{ id: 'ctpvNm', title: '시도명' },
{ id: 'ctpvNm', title: '시도' },
// { id: 'sgg_cd', title: '시군구코드' },
{ id: 'sggNm', title: '시군구명' },
{ id: 'sggNm', title: '시군구' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
];
@@ -686,5 +686,6 @@ export const numberFields = [
'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt',
'precdngDry', 'rainFall', 'cntncTime', 'rainIn',
'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc',
'bizAmt', 'count'
'bizAmt', 'count',
'chmbr_xcnts', 'chmbr_ydnts'
];
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'
import InputSection from '../InputSection';
import { handleInputChange } from '../common';
import { getData } from "@/components/lib/api";
// import { useBizByFcltStore } from "../../../../store/StoreProvider";
import useStore from '../../../../store/useStore';
// 하수관로
const inputColumn = [
@@ -30,21 +30,7 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic
inspection: { data: inspectionData, setData: setInspectionData },
};
// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD);
const BIZ_SE_CD = [
{ lwpoCd: "100", lwpoCdNm: "관로기술진단" },
{ lwpoCd: "200", lwpoCdNm: "개대체" },
{ lwpoCd: "201", lwpoCdNm: "유지보수" },
{ lwpoCd: "203", lwpoCdNm: "청소, 준설" },
{ lwpoCd: "204", lwpoCdNm: "단순보수" },
];
// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD);
const BIZ_TY_CD = [
{ lwpoCd: "SV", lwpoCdNm: "용역" },
{ lwpoCd: "SF", lwpoCdNm: "자체" },
{ lwpoCd: "SC", lwpoCdNm: "공사" },
];
const { BIZ_SE_CD, BIZ_TY_CD } = useStore();
// 마운트 및 업데이트 시 API 호출
@@ -6,26 +6,125 @@ import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/
import { VerticalTable } from '@/components/ui/verticalTable';
import { getData } from "@/components/lib/api";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import { Label } from '@radix-ui/react-label';
const inputColumn = [
{ id: "prrtymngYn", title: "중점여부", type: "select", options: ["우수토실", "중점우수토실"] },
{ id: "swtrmtZoneCd", title: "처리구역", type: "input" },
];
const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: {
const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
featureData: any,
isMainSsc?: boolean,
rainMeasureData?: any,
chmbrXcnts?: string,
chmbrYdnts?: string,
onSaveClick?: () => void
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const [formData, setFormData] = useState<any>({});
const [mntrngsData, setMntrngsData] = useState<any[]>([]); // 수량/수질 측정 정보
const [rainMeasureData, setRainMeasureData] = useState<any>({}); // 강우, 측정 정보
const [mntrngsData, setMntrngsData] = useState<any[]>([]); // 수량/수질측정 정보
const [totalCount, setTotalCount] = useState<number>(0);
const [totalDetailCount, setTotalDetailCount] = useState<number>(0);
const [selectedRowIndex, setSelectedRowIndex] = useState<number>(-1);
const [curPage, setCurPage] = useState<number>(1);
const [curDetailPage, setCurDetailPage] = useState<number>(1);
const [isResetPage, setIsResetPage] = useState<boolean>(true);
const [isResetDetailPage, setIsResetDetailPage] = useState<boolean>(true);
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
// console.log('featureData data received:', featureData); // 추가 데이터 확인용
// console.log('rainMeasureData received:', rainMeasureData); // 추가 데이터 확인용
console.log('featureData', featureData);
// 강우, 측정 정보 API 호출 함수
const fetchRainMeasureData = async (chmbrXcnts: string, chmbrYdnts: string) => {
setIsResetDetailPage(true);
setIsResetSelection(true);
if (!chmbrXcnts || !chmbrYdnts) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts });
return [];
}
try {
const queryParams = new URLSearchParams({
chmbrXcnts: chmbrXcnts,
chmbrYdnts: chmbrYdnts,
page: `${curPage - 1}`,
size: '3',
});
console.log('queryParams', queryParams.toString());
console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
console.log('추가 데이터 API 호출 성공:', result);
const processedDataArray: any[] = result.content.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`,
};
});
setRainMeasureData(processedDataArray);
setTotalCount(result?.totalElements || 0);
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
};
// 수량/수질측정 정보 API 호출 함수
const fetchMntrngsData = async (adstDstrCd: string, manageNo: string) => {
setIsResetDetailPage(true);
if (!adstDstrCd || !manageNo) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo });
return [];
}
try {
const queryParams = new URLSearchParams({
page: `${curDetailPage - 1}`,
size: '3',
});
console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`);
console.log('SSC 추가 데이터 API 호출 성공:', result);
// 각 아이템 전처리
const processedDataArray = result.content.map((data: any) => {
// 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
return {
...formattedItem,
};
});
console.log('processedDataArray', processedDataArray);
setMntrngsData(processedDataArray);
setTotalDetailCount(result?.totalElements || 0);
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
};
// 🎯 추가 데이터는 별도 useEffect로 비동기 로딩
useEffect(() => {
// 강우/측정 정보 데이터 호출
if (isMainSsc && chmbrXcnts && chmbrYdnts) {
fetchRainMeasureData(chmbrXcnts, chmbrYdnts)
}
}, [chmbrXcnts, chmbrYdnts]);
const openModal = () => {
setIsModalOpen(true);
@@ -46,6 +145,19 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: {
}
}, [featureData]);
// featureData가 변경될 때 formData 초기화
useEffect(() => {
// showTable이 true일 때만 fetch (최초 렌더 방지)
setIsResetDetailPage(false);
setSelectedRowIndex(-1);
if (chmbrXcnts && chmbrYdnts) fetchRainMeasureData(chmbrXcnts, chmbrYdnts);
}, [curPage]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
if (chmbrXcnts && chmbrYdnts && selectedRowIndex !== -1) fetchMntrngsData(rainMeasureData[selectedRowIndex].adstDstrCd, rainMeasureData[selectedRowIndex].manageNo);
}, [curDetailPage]);
// 폼 데이터가 변경될 때 호출될 핸들러
const handleFormDataChange = (newFormData: any) => {
setFormData(newFormData);
@@ -62,120 +174,70 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: {
// 테이블 셀 클릭 핸들러 추가
const handleCellClick = async (index: number | undefined, headId: string) => {
if (index !== undefined && rainMeasureData) {
const data = await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo);
setMntrngsData(data);
await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo);
setSelectedRowIndex(index);
}
};
// 🎯 추가 데이터 API 호출 함수 (독립적으로 실행)
const fetchMntrngsData = useCallback(async (adstDstrCd: string, manageNo: string) => {
if (!adstDstrCd || !manageNo) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo });
return [];
}
try {
const queryParams = new URLSearchParams({
pageable: `{}`,
});
console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`);
console.log('SSC 추가 데이터 API 호출 성공:', result);
// 🎯 result가 페이지네이션 객체인 경우 content 배열 추출
let dataArray: any[] = [];
if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) {
dataArray = result.content;
} else if (Array.isArray(result)) {
dataArray = result;
} else {
console.warn('예상치 못한 API 응답 형식:', result);
return [];
}
// 🎯 각 아이템을 전처리하여 반환
const processedDataArray = dataArray.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
};
});
console.log('processedDataArray', processedDataArray);
return processedDataArray;
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
}, []);
const renderModalBody = () => (
<div className='grid grid-cols-1 gap-4 p-4'>
{/* 강우정보 테이블 */}
<UiTable
headName='강우정보'
tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []}
headlist={untrtSwgRainCol3}
pageSize={10}
total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0}
cellClick={handleCellClick}
onPageChange={function (page: number): void {
console.log('Rain data page change:', page);
}}
/>
<div>
<Label className='text-3xl font-medium text-label '>강우/측정 정보</Label>
{/* 강우정보 테이블 */}
<UiTable
headName='강우정보'
tableData={rainMeasureData}
headlist={untrtSwgRainCol3}
pageSize={3}
total={0}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
index={selectedRowIndex}
/>
{/* 측정정보 테이블 */}
<UiTable
headName='측정정보'
tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []}
headlist={untrtSwgRainCol4}
pageSize={10}
total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0}
cellClick={handleCellClick}
// onPageChange={setCurPage}
onPageChange={function (page: number): void {
console.log('Rain data page change:', page);
}}
resetPageNo={false}
// index={curPage}
>
{/* 커스텀 헤더 */}
<TableRow>
<TableHead colSpan={5} className="text-center bg-blue-100 font-bold">수량</TableHead>
<TableHead colSpan={3} className="text-center bg-blue-100 font-bold">수질</TableHead>
</TableRow>
<TableRow>
<TableHead className="text-center">측정시작일시</TableHead>
<TableHead className="text-center">측정종료일시</TableHead>
<TableHead className="text-center">측정간격(분)</TableHead>
<TableHead className="text-center">측정기기설치위치</TableHead>
<TableHead className="text-center">유량계 종류 및 모델명</TableHead>
<TableHead className="text-center">채수시작일시</TableHead>
<TableHead className="text-center">채수종료일시</TableHead>
<TableHead className="text-center">채수횟수</TableHead>
</TableRow>
</UiTable>
{/* 측정정보 테이블 */}
<UiTable
headName='측정정보'
tableData={rainMeasureData}
headlist={untrtSwgRainCol4}
pageSize={3}
total={totalCount}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
index={selectedRowIndex}
>
{/* 커스텀 헤더 */}
<TableRow>
<TableHead colSpan={5} className="text-center bg-blue-100 font-bold">수량</TableHead>
<TableHead colSpan={3} className="text-center bg-blue-100 font-bold">수질</TableHead>
</TableRow>
<TableRow>
<TableHead className="text-center">측정시작일시</TableHead>
<TableHead className="text-center">측정종료일시</TableHead>
<TableHead className="text-center">측정간격(분)</TableHead>
<TableHead className="text-center">측정기기설치위치</TableHead>
<TableHead className="text-center">유량계 종류 및 모델명</TableHead>
<TableHead className="text-center">채수시작일시</TableHead>
<TableHead className="text-center">채수종료일시</TableHead>
<TableHead className="text-center">채수횟수</TableHead>
</TableRow>
</UiTable>
</div>
{/* 수량/수질측정 정보 테이블 */}
<UiTable
headName='수량/수질측정 정보'
tableData={Array.isArray(mntrngsData) ? mntrngsData : []}
tableData={mntrngsData}
headlist={untrtSwgRainCol5}
pageSize={10}
total={Array.isArray(mntrngsData) ? mntrngsData.length : 0}
pageSize={3}
total={totalDetailCount}
cellClick={undefined}
onPageChange={function (page: number): void {
console.log('Quality data page change:', page);
}}
onPageChange={setCurDetailPage}
resetPageNo={isResetDetailPage}
/>
</div>
)
@@ -1,13 +1,15 @@
'use client'
import React, { useState,useEffect } from 'react'
import Modal from '@/components/ui/modal';
import { VerticalTable } from '@/components/ui/verticalTable';
import { UiTable } from '@common/business_components/ui';
import { Button, TableCell } from '@common/components/ui';
import React, { useState } from 'react'
import { Button } from '@common/components/ui';
import { useDcTree, flattenFolders, getFileDownload, addFileName, type FlattenedFolder} from '../../searchModal/searchModalReportApi';
// 하수처리시설
const headList = [
{ id: "title", title: "하수도정비 기본계획보고서" },
{ id: "cad", title: "하수도대장 평면도" },]
];
const modalHeadList = [
{ id: "no", title: "번호" },
{ id: "year", title: "승인연도" },
@@ -16,9 +18,10 @@ const modalHeadList = [
{ id: "date", title: "입력시간" },
]
const SwtrmtFclt = () => {
const SwtrmtFclt = ({ gisObject }: { gisObject: any }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalTitle, setModalTitle] = useState<string>('');
const [dcDataOne, setDcDataOne] = useState<FlattenedFolder|undefined>({});
const openModal = (title: string) => {
setIsModalOpen(true);
setModalTitle(title)
@@ -26,22 +29,59 @@ const SwtrmtFclt = () => {
const closeModal = () => {
setIsModalOpen(false);
};
const ctpvNm = gisObject?.ctpv_nm;
//const reportOriginData = useDcTree(ctpvNm);
const reportOriginData = useDcTree("강원특별자치도");
useEffect(() => {
const fetch = async () => {
if (!reportOriginData || reportOriginData.length === 0) {
return;
}
const reportData = flattenFolders(reportOriginData);
let filterReportData = [...reportData];
if (gisObject && gisObject.sgg_nm !== undefined && gisObject.sgg_nm !== "") { // 시군구검색
//const sggNm = gisObject?.sgg_nm;
const sggNm = "강릉시";
filterReportData = filterReportData.filter(item => item.sigungu === sggNm);
}
addFileName(filterReportData).then(data => {
filterReportData = data.filter(item => item.filePath !== undefined && item.filePath !== "");
if (filterReportData !== undefined && filterReportData.length > 0) {
setDcDataOne(filterReportData[0]);
}
});
console.log("filterReportData", filterReportData);
};
fetch();
}, [reportOriginData]);
// 하수도정비기본계획 filedownload 핸들러
const handleFileDownloadClick = () => {
if (dcDataOne && dcDataOne !== undefined && dcDataOne.fileSn !== undefined && dcDataOne.fileSn !== ""
&& dcDataOne.filePath !== undefined && dcDataOne.filePath !== "" && dcDataOne.fileName !== undefined && dcDataOne.fileName !== "" ) {
console.log("dcDataOne", dcDataOne);
getFileDownload(String(dcDataOne.fileSn), String(dcDataOne.fileName), String(dcDataOne.filePath));
}
};
//cad: <Button onClick={() => openModal('하수도대장 평면도')}>평면도</Button>,
return (
<div>
<VerticalTable children={{
title: <Button onClick={() => openModal('하수도정비 기본계획보고서')}>보고서</Button>,
cad: <Button onClick={() => openModal('하수도대장 평면도')}>평면도</Button>,
<VerticalTable
children={{
title: <Button className="gray" onClick={() => handleFileDownloadClick()}>다운로드</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}>
{/* <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 {
<UiTable headName={modalTitle} tableData={[]} headlist={modalHeadList} pageSize={20} total={0} cellClick={undefined} onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}
}></UiTable>
</div>
</Modal>
</Modal> */}
</div>
)
}
@@ -5,13 +5,13 @@ import { VerticalTable } from "@/components/ui/verticalTable";
import { useGisContext } from "@common/business_components/ui/map/Gis";
import { Button } from "@common/components/ui";
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
import { getLayerContentComponent } from "./components/common";
import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "./components/common/table";
import InfilChart from './components/popupComponent/chart/infilChart';
import RdiiChart from './components/popupComponent/chart/rdiiChart';
import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore";
import { getLayerContentComponent } from "../common";
import { SwtrmtFcltCol, SwtrmtFcltCol2, SwtrmtPrcsZoneCol, SwtrmtPrcsZoneCol2, untrtSwgRainCol1, untrtSwgRainCol2, MsSwagePplCol, MsSwagePplCol2, ExclnSewagePumpCol, ExclnSewagePumpCol2, DrtwSewagePumpCol, DrtwSewagePumpCol2, MsSwageMhlCol, MsSwageMhlCol2, stofcbrCol, stofcbrCol2, MS_SWAGE_MHL_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from "../common/table";
import InfilChart from './chart/infilChart';
import RdiiChart from './chart/rdiiChart';
import { useInfilChartModalStore, useRdiiChartModalStore } from "./chartModalStore";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import { codeMapper } from "./components/common/codeMapper";
import { codeMapper } from "../common/codeMapper";
import { searchUpperPpls } from '@common/business_components/ui/map/Gis';
import LoadingOverlay from '@/components/ui/LoadingBar';
@@ -21,7 +21,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
const [layerSpecificNm, setLayerSpecificNm] = useState<string>(""); // 레이어 이름 상태
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
const [rainMeasureData, setRainMeasureData] = useState<any[]>([]); // 강우/측정 측정 정보
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [buttonChk, setButtonChk] = useState(true)
@@ -33,18 +32,17 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
// 🎯 InfilChart 및 RdiiChart 관련 상태 추가
const { setIsOpen: setIsModalInfil } = useInfilChartModalStore();
const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore();
const [curPage, setCurPage] = useState(1); // 현재 페이지 상태
const [isClicked, setIsClicked] = useState(true); // 클릭 여부 상태
const [isLoading, setIsLoading] = useState(false); // 로딩 상태
const [chmbrXcnts, setChmbrXcnts] = useState<string>(''); // 로딩 상태
const [chmbrYdnts, setChmbrYdnts] = useState<string>(''); // 로딩 상태
// 🎯 관로관리번호 추출
const pplMngNo = featureData?.values_?.fclt_mng_no || "";
// const featureId = featureData.getId(); // 피처의 id 가져오기
const featureId = typeof featureData.getId === 'function'
? featureData.getId()
: featureData.fid;
console.log('featureId', featureId);
// 🎯 데이터 전처리 메모이제이션
const processedData = useMemo(() => {
@@ -263,18 +261,9 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
data: { ...processedFormattedData }
}));
// 강우/측정 정보 데이터 호출
if (tableData?.chmbr_xcnts && tableData?.chmbr_ydnts) {
fetchRainMeasureData(tableData.chmbr_xcnts, tableData.chmbr_ydnts)
.then(response => {
console.log('additionalResponse', response);
setRainMeasureData(response || []);
})
.catch(error => {
console.error('추가 데이터 로딩 실패:', error);
setRainMeasureData([]);
});
}
setChmbrXcnts(data.chmbr_xcnts);
setChmbrYdnts(data.chmbr_ydnts);
break;
default:
break;
@@ -317,76 +306,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
fetchData();
}, [featureId, processedData]);
// 🎯 추가 데이터 API 호출 함수
const fetchRainMeasureData = useCallback(async (chmbrXcnts: string, chmbrYdnts: string) => {
if (!chmbrXcnts || !chmbrYdnts) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts });
return [];
}
try {
const queryParams = new URLSearchParams({
chmbrXcnts: chmbrXcnts,
chmbrYdnts: chmbrYdnts,
pageable: `{}`,
});
console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
console.log('추가 데이터 API 호출 성공:', result);
// 🎯 result가 페이지네이션 객체인 경우 content 배열 추출
let dataArray: any[] = [];
if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) {
dataArray = result.content;
} else if (Array.isArray(result)) {
dataArray = result;
} else {
console.warn('예상치 못한 API 응답 형식:', result);
return [];
}
// 🎯 각 아이템을 전처리하여 반환
const processedDataArray = dataArray.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`,
};
});
return processedDataArray;
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
}, []);
// 🎯 추가 데이터는 별도 useEffect로 비동기 로딩
useEffect(() => {
if (layerNm === '중점우수토실' && featureData?.values_) {
const data = featureData.values_;
if (data?.chmbr_xcnts && data?.chmbr_ydnts) {
// 🎯 추가 데이터를 비동기로 로딩 (UI 블로킹 없음)
fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts)
.then(response => {
console.log('추가 데이터 로딩 완료:', response);
setRainMeasureData(response || []);
})
.catch(error => {
console.error('추가 데이터 최종 로딩 실패:', error);
setRainMeasureData([]);
});
}
}
}, [layerNm, featureData?.values_, fetchRainMeasureData]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
@@ -434,7 +353,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
else if (layerNm === '하수펌프장') apiEndpoint = 'v1/fcltpump';
else if (layerNm === '하수맨홀') apiEndpoint = 'v1/fcltmhl';
bodyData['fid'] = featureId.toString().split('.').pop() || null;
bodyData['fcltMngNo'] = featureData.values_.fclt_mng_no.toString() || null;
// 빈 키 제거
if (bodyData[""] !== undefined) delete bodyData[""];
@@ -484,7 +403,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
</h3>
</div>
<div className="flex items-center space-x-3">
{/* 🎯 하수관로일 때만 Infil/RDII 버튼 표시 */}
{/* 하수관로일 때만 Infil/RDII 버튼 표시 */}
{layerNm === '하수관로' && (
<>
<Button onClick={() => setIsModalInfil(true)}
@@ -520,9 +439,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
</div>
<Button
onClick={onClose}
// className="text-white hover:text-gray-300 focus:outline-none"
className="flex items-center space-x-4 text-white hover:text-gray-300 focus:outline-none"
// className="absolute top-8 right-4 text-white hover:text-gray-300 focus:outline-none"
size="xs"
>
✕
@@ -555,13 +472,14 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
featureData={fetchData}
isMainSsc={isMainSsc}
isDrtwSewagePump={isDrtwSewagePump}
rainMeasureData={rainMeasureData}
onSaveClick={handleSaveClick}
chmbrXcnts={chmbrXcnts}
chmbrYdnts={chmbrYdnts}
gisObject={featureData.values_}
/>
}
</div>
{/* 🎯 InfilChart 및 RdiiChart 컴포넌트 추가 */}
<InfilChart pplMngNo={pplMngNo} />
<RdiiChart pplMngNo={pplMngNo} />
</div>
+13 -4
View File
@@ -5,7 +5,9 @@ import { getData } from "../../components/lib/api";
import { useCallback, useEffect, useRef, useState } from "react";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import SearchModal from "./searchModal/searchModal";
import FeatureClickEventPopup from "./featureClickEventPopup";
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
import { SearchBar } from "./searchBar/searchBar";
import { WaterDepth } from "./waterDepth/waterDepth";
// 타입 정의 추가
export type LyrStngProps = {
@@ -39,6 +41,11 @@ const Map = () => {
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
const data = await getData("v1/map/lyrStngs/SN_NETWORK");
@@ -48,9 +55,6 @@ const Map = () => {
}
};
useEffect(() => {
fetchData();
}, []);
// SearchModal, GisContent 등에서 호출
const onFeatureSelect = (data: any) => {
let map = gisMaps.current;
@@ -113,6 +117,8 @@ const Map = () => {
setIsOpen(true);
setIsSearchModalOpen(false);
};
return (
<div className="bg-container">
<Gis
@@ -130,6 +136,9 @@ const Map = () => {
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={undefined}
searchBar={SearchBar}
// waterDepth={WaterDepth}
waterDepth={undefined}
/>
</div>
);
@@ -0,0 +1,32 @@
"use client";
import { useState } from "react";
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
export function SearchBar() {
const [ctpv, setCtpv] = useState<number>(0);
const [sgg, setSgg] = useState<number>(0);
const [sggName, setSggName] = useState<string>('');
const [selectedSwagePrcsFclt, setSelectedSwagePrcsFclt] = useState<string | number>('0');
return (
<>
<div className="flex items-center gap-4 bg-white min-h-[48px] min-w-[600px] px-5 py-3 rounded-lg shadow">
<CtpvSggPrcsFcltSelectBox
sido={ctpv}
sigungu={sgg}
sggName={sggName}
onSidoChange={(sidoId) => {
setCtpv(sidoId);
setSgg(0);
}}
onSigunguChange={setSgg}
setSggName={setSggName}
selectedSwagePrcsFclt={selectedSwagePrcsFclt}
onSwagePrcsFcltChange={setSelectedSwagePrcsFclt}
/>
</div>
</>
);
}
@@ -1,6 +1,4 @@
import React from 'react';
import { MS_SWAGE_MHL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table';
import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui';
import { Button, Input, Label } from '@common/components';
@@ -21,7 +19,6 @@ export const BASIC_TYPES = [
];
interface BasicTabProps {
open?: boolean;
basicType: string;
city: number;
sgg: number;
@@ -33,13 +30,11 @@ interface BasicTabProps {
var2: string;
yearFilter: { start: string; end: string; };
handleSearch: () => void;
// onOpenChange?: (open: boolean) => void;
// onFeatureSelect?: (row: any) => void;
// setTableData?: (data: any[]) => void;
// setTableCount?: (count: number) => void;
setBasicType: (type: string) => void;
setCity: (city: number) => void;
setCtpvName: (name: string) => void;
setSgg: (sgg: number) => void;
setSggName: (name: string) => void;
setYearFilter: (filter: { start: string; end: string }) => void;
setStart1: (value: string) => void;
setStart2: (value: string) => void;
@@ -50,7 +45,6 @@ interface BasicTabProps {
}
const BasicTab: React.FC<BasicTabProps> = ({
// open,
basicType,
city,
sgg,
@@ -62,13 +56,11 @@ const BasicTab: React.FC<BasicTabProps> = ({
var2,
yearFilter,
handleSearch,
// onOpenChange,
// onFeatureSelect,
// setTableData,
// setTableCount,
setBasicType,
setCity,
setCtpvName,
setSgg,
setSggName,
setYearFilter,
setStart1,
setStart2,
@@ -78,7 +70,7 @@ const BasicTab: React.FC<BasicTabProps> = ({
setVar2,
}) => {
return (
<div className="relative border rounded-lg p-6 w-full h-[10vh]">
<div className="relative border rounded-lg p-5 w-full h-[10vh]">
{/* 구분 */}
<div className="mb-3 flex items-center justify-between gap-4">
<div className="flex items-center">
@@ -124,12 +116,18 @@ const BasicTab: React.FC<BasicTabProps> = ({
start2={start2}
end1={end1}
end2={end2}
city={city}
sgg={sgg}
yearFilter={yearFilter}
setVar1={setVar1}
setStart1={setStart1}
setStart2={setStart2}
setEnd1={setEnd1}
setEnd2={setEnd2}
setCity={setCity}
setCtpvName={setCtpvName}
setSgg={setSgg}
setSggName={setSggName}
setYearFilter={setYearFilter}
handleSearch={handleSearch}
/>
@@ -1,40 +1,15 @@
import React from 'react';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui';
import { Button, Input, Label } from '@common/components';
import React from 'react';
//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi';
// import { useBizByFcltStore } from "../../../store/StoreProvider";
import useStore from '../../../store/useStore';
// 운영정보 '구분' 선택 목록
// const WORK_TYPES = [
// { val: "maintenance", text: "보수 및 갱신" },
// { val: "cleaning", text: "청소 및 준설" },
// { val: "inspection", text: "점검 및 진단" },
// ];
export const WORK_TYPES = [
{ val: "['200', '201', '204']", text: "보수 및 갱신" },
{ val: "['100']", text: "점검 및 진단" },
{ val: "['203']", text: "청소 및 준설" },
];
// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD);
export const BIZ_SE_CD = [
{ lwpoCd: "100", lwpoCdNm: "관로기술진단" },
{ lwpoCd: "200", lwpoCdNm: "개대체" },
{ lwpoCd: "201", lwpoCdNm: "유지보수" },
{ lwpoCd: "203", lwpoCdNm: "청소, 준설" },
{ lwpoCd: "204", lwpoCdNm: "단순보수" },
];
// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD);
export const BIZ_TY_CD = [
{ lwpoCd: "0", lwpoCdNm: "전체" },
{ lwpoCd: "SV", lwpoCdNm: "용역" },
{ lwpoCd: "SF", lwpoCdNm: "자체" },
{ lwpoCd: "SC", lwpoCdNm: "공사" },
];
export const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
val: item.lwpoCd,
text: item.lwpoCdNm,
}));
// 운영정보 - 사업 현황
export const operationTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
@@ -80,15 +55,11 @@ export const operationInspectionTableHeadlist = [
];
interface OperationTabProps {
open: boolean;
workType: string;
var1: string;
var2: string;
yearFilter: { start: string; end: string; };
handleSearch: () => void;
// onOpenChange: (open: boolean) => void;
// setTableData: (data: any[]) => void;
// setTableCount: (count: number) => void;
setWorkType: (type: string) => void;
setYearFilter: (filter: { start: string; end: string }) => void;
setVar1: (value: string) => void;
@@ -96,26 +67,30 @@ interface OperationTabProps {
}
const OperationTab: React.FC<OperationTabProps> = ({
open,
workType,
var1,
var2,
yearFilter,
handleSearch,
// onOpenChange,
// setTableData,
// setTableCount,
setWorkType,
setVar1,
setVar2,
setYearFilter,
}) => {
const { BIZ_TY_CD } = useStore();
const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
val: item.lwpoCd,
text: item.lwpoCdNm,
}));
return (
<div className="relative flex flex-col border rounded-lg p-6 w-full h-[10vh]">
<div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]">
<div className="flex items-center mb-3">
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">업무구분</Label>
<SelectBox
className="w-80 mr-8"
className="w-40 mr-8"
selectClass="rounded-lg bg-white text-xl"
label=""
selectArray={WORK_TYPES.map((w) => ({
@@ -165,7 +165,7 @@ const ReportTab: React.FC<BasicTabProps> = ({ open }) => {
return (
<>
<div className="relative flex flex-col border rounded-lg p-6 w-full h-[10vh]">
<div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]">
<div className="flex items-center mb-3">
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
<SelectBox className="w-60 mr-8"
@@ -3,24 +3,22 @@ import { UiModal, UITab, UiTable } from '@common/business_components/ui';
import axios from 'axios';
import React, { useEffect, useMemo, useState } from 'react';
import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table';
import { type SelectDataType } from '../commonStore';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table';
import { getData } from "@/components/lib/api";
import BasicTab, { BASIC_TYPES } from './basicTab';
import OperationTab, { BIZ_TY_CD, operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab, { reportTableHeadlist } from './reportTab';
import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab from './reportTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '../../../store/useStore';
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
// 탭
const TABS = [
{ val: "basic", text: "기본정보" },
{ val: "operation", text: "운영정보" },
{ val: "report", text: "하수도정비기본계획보고서" },
];
// 날짜 조합 함수
const getDateString = (
year: string,
@@ -63,12 +61,10 @@ const SearchModal: React.FC<SearchModalProps> = ({
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
const [isResetDetailSelection, setIsResetDetailSelection] = useState<boolean>(false);
const [detailBizId, setDetailBizId] = useState(0);
const [city, setCity] = useState(0);
const [sgg, setSgg] = useState(0);
const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report
const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report
const [gubun, setGubun] = useState(""); // 하수정비기본계획 report
const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report
const [city, setCity] = useState<number>(0);
const [ctpvName, setCtpvName] = useState<string>('');
const [sgg, setSgg] = useState<number>(0);
const [sggName, setSggName] = useState<string>('');
const [start1, setStart1] = useState("");
const [start2, setStart2] = useState("");
const [end1, setEnd1] = useState("");
@@ -79,7 +75,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
const [var4, setVar4] = useState("");
// 시작, 종료
const [yearFilter, setYearFilter] = useState({ start: "", end: "" });
const [filteredSiguns, setFilteredSiguns] = useState<SelectDataType[]>([{ val: '0', text: '전체', sido: '0' }]);
const { BIZ_TY_CD } = useStore();
// 오늘 날짜 구하기
@@ -341,13 +338,17 @@ const SearchModal: React.FC<SearchModalProps> = ({
cqlFilters.push(`fclt_cpct BETWEEN ${start1} AND ${end1}`);
else if (start1) cqlFilters.push(`fclt_cpct >= ${start1}`);
else if (end1) cqlFilters.push(`fclt_cpct <= ${end1}`);
// 처리구역 면적
if (start2 && end2)
cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`);
else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`);
else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`);
// 하수처리시설명
if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`);
// 시도
if (ctpvName) cqlFilters.push(`ctpv_nm ILIKE '%${ctpvName}%'`);
if (sggName) cqlFilters.push(`sgg_nm ILIKE '%${sggName}%'`);
// // 처리구역 면적
// if (start2 && end2)
// cqlFilters.push(`prcsZoneAreaSum BETWEEN ${start2} AND ${end2}`);
// else if (start2) cqlFilters.push(`prcsZoneAreaSum >= ${start2}`);
// else if (end2) cqlFilters.push(`prcsZoneAreaSum <= ${end2}`);
// // 하수처리시설명
// if (var1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${var1}%'`);
return cqlFilters;
};
// 하수처리구역용 CQL 필터 생성
@@ -561,7 +562,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
{/* 기본정보 탭 */}
{activeTab === 'basic' && (
<BasicTab
open={open}
basicType={basicType}
city={city}
sgg={sgg}
@@ -575,7 +575,9 @@ const SearchModal: React.FC<SearchModalProps> = ({
handleSearch={handleSearch}
setBasicType={setBasicType}
setCity={setCity}
setCtpvName={setCtpvName}
setSgg={setSgg}
setSggName={setSggName}
setYearFilter={setYearFilter}
setStart1={setStart1}
setStart2={setStart2}
@@ -588,7 +590,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
{/* 운영정보 탭 */}
{activeTab === 'operation' && (
<OperationTab
open={open}
workType={workType}
var1={var1}
var2={var2}
@@ -79,7 +79,11 @@ export function flattenFolders(data: Folder[]): FlattenedFolder[] {
}
}
data.forEach((root) => traverse(root, {}, 0));
if (data && data.length > 0 && data[0].clsfSystmNm == "기본계획") { // data가 비어있지 않고 children이 있는 경우에만 처리
data.forEach((root) => traverse(root, {}, 0));
} else {
data.forEach((root) => traverse(root, {}, 1));
}
return result;
}
@@ -96,13 +100,15 @@ export const useDcTree = (clsfSystmNm:string) => {
if (result) {
setData(result);
}
console.log("useDcTree호출");
console.log("clsfSystmNm:", clsfSystmNm, "data:", result);
};
fetch();
console.log("useDcTree호출");
}, [clsfSystmNm]);
return data;
};
export const getDcFile = async (clsfSystmSn:string) => {
const queryString = 'clsfSystmSn='+clsfSystmSn;
const result = await getDcData(`/api/v1/dc/file${queryString ? `?${queryString}` : ''}`);
@@ -119,7 +125,7 @@ export const addFileName = async (data:FlattenedFolder[]) => {
})
);
return fileReportData;
}
};
export const getDcFileprcs = async (fileSn:string) => {
const queryString = 'fileSn='+fileSn;
const result = await getDcData(`/api/v1/dc/fileprcs${queryString ? `?${queryString}` : ''}`);
@@ -1,8 +1,10 @@
// 하수처리시설
import React from 'react';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { Button, Input, Label } from '@common/components';
import React from 'react';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
interface SwagePrcsFcltProps {
var1: string;
@@ -10,36 +12,53 @@ interface SwagePrcsFcltProps {
start2: string;
end1: string;
end2: string;
city: number;
sgg: number;
yearFilter: { start: string; end: string };
setVar1: (value: string) => void;
setStart1: (value: string) => void;
setStart2: (value: string) => void;
setEnd1: (value: string) => void;
setEnd2: (value: string) => void;
setCity: (value: number) => void;
setCtpvName: (name: string) => void;
setSgg: (value: number) => void;
setSggName: (name: string) => void;
setYearFilter: (filter: { start: string; end: string }) => void;
handleSearch: () => void;
}
const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({
var1,
start1,
start2,
end1,
end2,
city,
sgg,
yearFilter,
setVar1,
setStart1,
setStart2,
setEnd1,
setEnd2,
setCity,
setCtpvName,
setSgg,
setSggName,
setYearFilter,
handleSearch,
}) => {
return (
<div className="mb-3">
{/* 가동개시일자 */}
<div className="mb-2 flex items-center">
<CtpvSggPrcsFcltSelectBox
sido={city}
sigungu={sgg}
onSidoChange={(sidoId) => {
setCity(sidoId);
setSgg(0);
}}
onSigunguChange={setSgg}
setCtpvName={setCtpvName}
setSggName={setSggName}
/>
{/* 가동개시일자 */}
<Label className="mr-2 w-auto text-xl text-gray-600">가동개시일자</Label>
<YearRangeSelectBox
start={yearFilter.start}
@@ -89,57 +108,6 @@ const SwagePrcsFclt: React.FC<SwagePrcsFcltProps> = ({
}
}}
/>
{/* 처리구역 면적 */}
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">처리구역 면적(m²)</Label>
<Input
className="w-[60px] !text-xl placeholder:text-center"
placeholder="입력"
value={start2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
setStart2(val);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Enter 키 기본 동작 방지
handleSearch();
}
}}
/>
<span className="mx-2 text-xl font-semibold">~</span>
<Input
className="mr-8 w-[60px] !text-xl placeholder:text-center"
placeholder="입력"
value={end2}
onChange={(e) => {
// 숫자만 입력 허용
const val = e.target.value.replace(/[^0-9]/g, "");
setEnd2(val);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Enter 키 기본 동작 방지
handleSearch();
}
}}
/>
{/* 하수처리시설명 */}
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={var1}
onChange={(e) => {
setVar1(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Enter 키 기본 동작 방지
handleSearch();
}
}}
/>
{/* 조회 버튼 */}
<div className="flex-1 flex justify-end">
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
@@ -1,6 +1,6 @@
// 하수처리구역
import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox';
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox';
import { Button, Input, Label } from '@common/components';
import React from 'react';
@@ -31,30 +31,15 @@ const swagePrcsZone: React.FC<swagePrcsZoneProps> = ({
return (
<div className="mb-3">
<div className="mb-2 flex items-center">
<SidoSigunguSelectBox
<CtpvSggPrcsFcltSelectBox
sido={city}
sigungu={sgg}
onSidoChange={(sidoId) => {
onSidoChange={(sidoId: number) => {
setCity(sidoId);
setSgg(0);
}}
onSigunguChange={setSgg}
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리구역명</Label>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={var1}
onChange={(e) => {
setVar1(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Enter 키 기본 동작 방지
handleSearch();
}
}}
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리시설명</Label>
<Input
className='!text-xl w-[120px] placeholder:text-center mr-8'
@@ -70,6 +55,21 @@ const swagePrcsZone: React.FC<swagePrcsZoneProps> = ({
}
}}
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">하수처리구역명</Label>
<Input
className="mr-8 w-[100px] !text-xl placeholder:text-center"
placeholder="입력"
value={var1}
onChange={(e) => {
setVar1(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault(); // Enter 키 기본 동작 방지
handleSearch();
}
}}
/>
{/* 조회 버튼 */}
<div className="flex-1 flex justify-end">
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
@@ -0,0 +1,62 @@
"use client";
import React, { useRef, useEffect, useState } from "react";
import { Label } from "@common/components/ui/label";
export function WaterDepth({ onClose }: { onClose?: () => void }) {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
return (
<>
<div className="fixed top-8 left-8 z-50 bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
<div className="card-header flex items-center justify-between">
<Label className="text-3xl text-gray-900 font-bold">침수심 범례</Label>
<button
type="button"
className="ml-2"
onClick={() => setIsPlaying((prev) => !prev)}
>
{isPlaying ? (
<img
src="/images/map/toolbar/pause_button.png"
alt="pause"
className="w-6 h-6 object-contain"
/>
) : (
<img
src="/images/map/toolbar/play_button_arrowhead.png"
alt="play"
className="w-6 h-6 object-contain"
/>
)}
</button>
</div>
<div className="border-b border-gray-300 mb-2" />
<div className="w-[140px] mx-auto flex flex-col items-center">
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">침 수 심</div>
<div className="space-y-1 w-full">
<LegendItem color="#0000ff" label="0.3~0.5m 미만" />
<LegendItem color="#00e8fe" label="0.5~1.0m 미만" />
<LegendItem color="#00fdb7" label="1.0~1.5m 미만" />
<LegendItem color="#00fe4e" label="1.5~2.0m 미만" />
<LegendItem color="#fce800" label="2.0~3.0m 미만" />
<LegendItem color="#fe0000" label="3.0m 이상" />
</div>
<div className="border-t-4 border-gray-900 mt-2 mx-auto w-full" />
</div>
</div>
</>
);
}
function LegendItem({ color, label }: { color: string; label: string }) {
return (
<div className="flex items-center gap-2 w-full">
<span
className="inline-block w-16 h-6"
style={{ backgroundColor: color, border: "1px solid #ccc" }}
/>
<span className="text-xl text-gray-900 font-bold">{label}</span>
</div>
);
}
+37 -144
View File
@@ -1,12 +1,9 @@
'use client'
import { getData } from '@/components/lib/api'
import ContentLayout from '@/components/ui/contentLayout'
import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm'
import { DatePicker, DateMonthPicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui'
import { DateMonthPicker, Form, SelectBox } from '@common/business_components/ui'
import ListDiv from '@common/business_components/ui/list_div'
import { Button, Label } from '@common/components/ui'
import { useEffect, useState, type ReactNode } from 'react'
import LoadingOverlay from '@/components/ui/LoadingBar'
import { useState, type ReactNode } from 'react'
import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox'
type TitleName = {
mainTitle: string
@@ -40,37 +37,26 @@ type BaseLayout = {
firstDivBtn?: boolean
selectBox?: SelectBox
showDatePicker?: boolean;
enoCd?: string | number
setEnoCd?: (enoCd: string) => void
ctpvCd?: string | number
setCtpvCd?: (ctpvCd: string) => void
ctpvCd: number;
sggCd: number;
sggName?: string;
sigungu?: ApiSelectOption
setSigungu?: (sigungu: ApiSelectOption) => void
selectedSwageTreatmentPlant?: string | number;
onSwageTreatmentPlantChange?: (value: string | number) => void;
selectedSwageProcessingZone?: string | number;
onSwageProcessingZoneChange?: (value: string | number) => void;
selectedSwagePrcsFclt?: string | number;
onSwagePrcsFcltChange?: (value: string | number) => void;
aplcnYmFrom?: string
setAplcnYmFrom?: (aplcnYmFrom: string) => void;
aplcnYmTo?: string
setAplcnYmTo?: (aplcnYmTo: string) => void;
isLoading?: boolean;
// Excel 다운로드 관련 props 추가
setCtpvCd: (ctpv: number) => void;
setSggCd: (sgg: number) => void;
setSggName?: (sggName: string) => void;
setIsLoading?: (isLoading: boolean) => void;
onExcelDownload?: () => void;
onDeleteFile?: () => void;
onAnalysis?: () => void;
selectedRowData?: any;
}
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
/**
*
* @param children
* @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀
* @param form : zod 스키마
* @param onSubmitEvent : 폼 이벤트 처리
* @param graphBtn : 하단에 그래프 버튼 유무 처리
* @returns
*/
const BaseLayout = ({
children,
titleName,
@@ -80,113 +66,25 @@ const BaseLayout = ({
analysisBtn,
selectBox,
showDatePicker = false,
enoCd,
setEnoCd,
ctpvCd,
setCtpvCd,
sggCd,
sggName,
sigungu,
setSigungu,
selectedSwageTreatmentPlant,
onSwageTreatmentPlantChange,
selectedSwageProcessingZone,
onSwageProcessingZoneChange,
selectedSwagePrcsFclt,
onSwagePrcsFcltChange,
aplcnYmFrom,
setAplcnYmFrom,
aplcnYmTo,
setAplcnYmTo,
isLoading,
// Excel 다운로드 관련 props 추가
setCtpvCd,
setSggCd,
setSggName,
setIsLoading,
onExcelDownload,
onDeleteFile,
onAnalysis,
selectedRowData
}: BaseLayout) => {
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('')
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 facilityRes = await 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 다운로드 실행
@@ -207,7 +105,6 @@ const BaseLayout = ({
return (
<Form {...form} className="min-h-screen flex flex-col">
<LoadingOverlay isLoading={isLoading ?? false} />
<ListDiv>
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
{selectBox?.title && (
@@ -215,10 +112,10 @@ const BaseLayout = ({
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
</div>
)}
<div className="flex items-center w-full">
<div className="ml-2 mt-2 flex items-center w-full">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="ml-2 mr-4 w-20 text-xl font-medium text-gray-600 whitespace-nowrap">상수사용기간</Label>
<Label className="mr-4 w-20 text-xl font-medium text-gray-600 whitespace-nowrap">상수사용기간</Label>
<DateMonthPicker
field={{
value: aplcnYmFrom || (() => { // aplcnYm이 없으면 현재 연월을 기본값으로 설정
@@ -253,24 +150,20 @@ const BaseLayout = ({
/>
</>
)}
<GovSidoSigunguSearchForm
isShowPart={true}
setRtnEnvPartValue={(val) => setEnoCd?.(val)}
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">하수처리장</Label>
<SelectBox
label={''}
value={selectedSwageTreatmentPlant}
selectArray={treatmentFacilityOptions}
className="w-[160px]"
selectClass="rounded-lg bg-white truncate"
onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)}
selectContentClass="z-[101]"
/>
{/* <SelectBox label={"하수처리구역"} value={selectedSwageProcessingZone} selectArray={processingZoneOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageProcessingZoneChange?.(val)} selectContentClass="z-[101]" /> */}
</GovSidoSigunguSearchForm>
<CtpvSggPrcsFcltSelectBox
sido={ctpvCd}
sigungu={sggCd}
sggName={sggName}
onSidoChange={(sidoId: number) => {
setCtpvCd(sidoId);
setSggCd(0);
}}
onSigunguChange={setSggCd}
setSggName={setSggName}
selectedSwagePrcsFclt={selectedSwagePrcsFclt}
onSwagePrcsFcltChange={onSwagePrcsFcltChange}
setIsLoading={setIsLoading}
/>
{selectBox?.searchChk && (
<div className='flex flex-1 justify-end mr-6'>
<Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button>
@@ -47,15 +47,14 @@ interface ApiSelectOption {
}
const IrSetting = () => {
const [input1, setInput1] = useState('');
const [input1, setInput1] = useState('0.8'); // 오수전환율 초기값
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 [select1, setSelect1] = useState<string | number>('0'); // 하수처리장코드
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
const { data: userInfo } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
@@ -115,11 +114,7 @@ const IrSetting = () => {
setSelectedDate(`${year}${month}`);
setInput1(''); // 입력 필드 초기화
setInput2(''); // 입력 필드 초기화
setSelect1('0'); // 선택 필드 초기화
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4(0);
setSelect5(0);
setSelect1(0);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
};
@@ -164,7 +159,7 @@ const IrSetting = () => {
setIsLoadingResult(true); // 로딩 상태 시작
// 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
if (!validateBasicSelectionsWithAlert(select1, {
requireEnv: true,
requireSido: true,
requireSigungu: true,
@@ -299,15 +294,11 @@ const IrSetting = () => {
// 새로운 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해서 마지막 부분만 사용
ctpvCd: (ctpvCd ? String(ctpvCd) : null),
sggCd: (sggCd ? String(sggCd) : null), // 시군구코드 (선택값)
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
aplcnYm: selectedDate, // 적용연월
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
@@ -346,17 +337,15 @@ const IrSetting = () => {
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}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwagePrcsFclt={select1}
onSwagePrcsFcltChange={setSelect1}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
setIsLoading={setIsLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div key="irsetting-layout-key" className="grid gap-4">
@@ -387,10 +376,7 @@ const IrSetting = () => {
accept={".xls, .xlsx, .xlsm, .xltx"}
fileIdx={2} />
</FormSection>
<FormSection
name='inputdate'
form={form}
titleNm='▷ 상수사용량 적용월'>
<FormSection name='inputdate' form={form} titleNm='▷ 상수사용량 적용월'>
{/* 연월 선택 라이브러리로 수정 필요 */}
<DateMonthPicker
field={{
@@ -403,10 +389,7 @@ const IrSetting = () => {
startYear={1900} // 시작 연도 설정
/>
</FormSection>
<FormSection
name='rtkSource'
form={form}
titleNm='▷ 오수전환율'>
<FormSection name='rtkSource' form={form} titleNm='▷ 오수전환율'>
<Input
value={input1}
onChange={(e) => {
@@ -434,10 +417,7 @@ const IrSetting = () => {
placeholder="예: 0.75"
/>
</FormSection>
<FormSection
name='analysis information'
form={form}
titleNm='▷ 분석 정보'>
<FormSection name='analysis information' form={form} titleNm='▷ 분석 정보'>
<Input
value={input2}
onChange={(e) => setInput2(e.target.value)}
+18 -30
View File
@@ -114,11 +114,10 @@ export default function RTKForm() {
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)
const [select1, setSelect1] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
const { data: userInfo } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
@@ -142,10 +141,6 @@ export default function RTKForm() {
]);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
setSelect1('0');
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4('0');
setSelect5('0');
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
@@ -182,7 +177,7 @@ export default function RTKForm() {
setIsLoadingResult(true); // 로딩 상태 시작
// 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
if (!validateBasicSelectionsWithAlert(select1, {
requireEnv: true,
requireSido: true,
requireSigungu: true,
@@ -295,16 +290,12 @@ export default function RTKForm() {
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
aplcnYm: formData.date, // 적용연월 (YYYYMM 형식)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
ctpvCd: ctpvCd ? String(ctpvCd) : null, // 시도코드 (선택값)
sggCd: sggCd ? String(sggCd) : null, // 시군구코드 (선택값)
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
? select1.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 || "", // 오수관망도 파일명
@@ -395,7 +386,6 @@ export default function RTKForm() {
}
return (
// <BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
<BaseLayout
selectBox={{ title: '분석 대상지역 선택' }}
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
@@ -403,17 +393,15 @@ export default function RTKForm() {
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}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwagePrcsFclt={select1}
onSwagePrcsFcltChange={setSelect1}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
setIsLoading={setIsLoadingResult}
onAnalysis={handleAnalysisClick}
>
{[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section">
@@ -1,5 +1,5 @@
'use client'
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
import { deleteData, getData } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
@@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
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'), // 하수처리구역
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
@@ -54,14 +48,10 @@ const tableColumn = [
]
const Result = () => {
const AUTH = useCookieAuth();
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
@@ -88,6 +78,9 @@ const Result = () => {
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
useEffect(() => {
// setCurPage(1);
@@ -127,11 +120,9 @@ const Result = () => {
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
"sggCd": sggCd === 0 ? null : sggCd,
"swageTmplntCd": processedSwageTmplntCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
@@ -163,7 +154,8 @@ const Result = () => {
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
})
.filter((item: any) => item.scsYn === 'Y');
// 선택된 행 초기화
setSelectedRowData(null);
@@ -289,21 +281,19 @@ const Result = () => {
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))}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwagePrcsFclt={form.watch("swageTmplntCd")}
onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))}
aplcnYmFrom={form.watch("aplcnYmFrom")}
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
aplcnYmTo={form.watch("aplcnYmTo")}
setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }}
isLoading={isLoadingResult}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
setIsLoading={setIsLoadingResult}
// Excel 다운로드 핸들러 추가
onExcelDownload={handleExcelDownload}
onDeleteFile={handleFileDelete}
@@ -1,6 +1,6 @@
'use client'
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { deleteData, getData } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
@@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
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'), // 하수처리구역
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
@@ -55,9 +49,6 @@ const Result = () => {
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
@@ -76,8 +67,6 @@ const Result = () => {
}
})
const AUTH = useCookieAuth();
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수l
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
@@ -85,6 +74,9 @@ const Result = () => {
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
useEffect(() => {
// setCurPage(1);
@@ -124,12 +116,9 @@ const Result = () => {
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
"sggCd": sggCd === 0 ? null : sggCd,
"swageTmplntCd": processedSwageTmplntCd,
// "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
@@ -162,7 +151,8 @@ const Result = () => {
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
})
.filter((item: any) => item.scsYn === 'Y');
// 선택된 행 초기화
setSelectedRowData(null);
@@ -289,21 +279,19 @@ const Result = () => {
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))}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwagePrcsFclt={form.watch("swageTmplntCd")}
onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))}
aplcnYmFrom={form.watch("aplcnYmFrom")}
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
aplcnYmTo={form.watch("aplcnYmTo")}
setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }}
isLoading={isLoadingResult} // 로딩 상태 전달
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
setIsLoading={setIsLoadingResult}
// Excel 다운로드 핸들러 추가
onExcelDownload={handleExcelDownload}
onDeleteFile={handleFileDelete}
+10
View File
@@ -1,11 +1,13 @@
"use client";
import React, { useEffect} from 'react';
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
import { PageLayout } from "@/shared/ui/page-layout";
import useStore from '../store/useStore';
export default function WithBaseLayout({
children,
@@ -18,6 +20,14 @@ export default function WithBaseLayout({
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore();
useEffect(() => {
fetchCtpvSggData();
fetchBIZ_SE_CD();
fetchBIZ_TY_CD();
}, []);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
@@ -87,10 +87,7 @@ export const validateMultipleSelectionsWithAlert = (
* @returns 검증 결과
*/
export const validateBasicSelections = (
select1: string,
select2: string,
select3: ApiSelectOption,
select4: string | number,
select1: string | number,
options: {
requireEnv?: boolean;
requireSido?: boolean;
@@ -108,21 +105,6 @@ export const validateBasicSelections = (
const validationConfigs: SelectionValidationConfig[] = [
{
value: select1,
description: '환경청',
required: requireEnv
},
{
value: select2,
description: '시도',
required: requireSido
},
{
value: select3,
description: '시군구',
required: requireSigungu
},
{
value: select4,
description: '하수처리장',
required: requireTreatmentPlant
}
@@ -133,18 +115,12 @@ export const validateBasicSelections = (
/**
* 기본 선택 항목들 검증 및 alert 표시
* @param select1 환경청코드
* @param select2 시도코드
* @param select3 시군구 객체
* @param select4 하수처리장코드
* @param select1 하수처리장코드
* @param options 각 항목의 필수 여부 설정
* @returns 검증 결과
*/
export const validateBasicSelectionsWithAlert = (
select1: string,
select2: string,
select3: ApiSelectOption,
select4: string | number,
select1: string | number,
options: {
requireEnv?: boolean;
requireSido?: boolean;
@@ -152,7 +128,7 @@ export const validateBasicSelectionsWithAlert = (
requireTreatmentPlant?: boolean;
} = {}
): boolean => {
const result = validateBasicSelections(select1, select2, select3, select4, options);
const result = validateBasicSelections(select1, options);
if (!result.isValid && result.errorMessage) {
alert(result.errorMessage);
return false;
+14 -5
View File
@@ -41,8 +41,6 @@ export const postData = async (url: string, data: object): Promise<any> => {
body: JSON.stringify(data),
};
const res = await fetch(`${BASE_URL}/${url}`, options);
console.log('res', res);
console.log('res.status', res.status);
if (res.ok) {
const resData = await res.json();
return resData;
@@ -85,7 +83,7 @@ export const deleteData = async (url: string, data?: object): Promise<any> => {
return res;
};
// RSONE DC API 호출
// DC API 호출
export const getDcData = async (url: string, data?: object): Promise<any> => {
const res = await fetch('/dc-service' + url, {
method: "GET",
@@ -97,9 +95,8 @@ export const getDcData = async (url: string, data?: object): Promise<any> => {
throw new Error('Failed to fetch data');
};
// RSONE DC API 호출
// CMS API 호출
export const getCmsData = async (url: string, data?: object): Promise<any> => {
console.log(url);
const res = await fetch('/cms-service' + url, {
method: "GET",
});
@@ -110,6 +107,18 @@ export const getCmsData = async (url: string, data?: object): Promise<any> => {
throw new Error('Failed to fetch data');
};
// GeoServer API 호출
export const getGeoData = async (url: string, data?: object): Promise<any> => {
const res = await fetch('/geoserver' + url, {
method: "GET",
});
if (res.ok) {
const data = await res.json();
return data;
}
throw new Error('Failed to fetch data');
};
export function getCookie(name: string) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match && match[2]) {
+5 -3
View File
@@ -103,8 +103,10 @@ export const formatDateString = (dateStr: string): string => {
}
const year = cleanStr.substring(0, 4);
const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거
const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거
const month = cleanStr.substring(4, 6); // parseInt로 앞의 0 제거
// const month = parseInt(cleanStr.substring(4, 6)); // parseInt로 앞의 0 제거
// const day = parseInt(cleanStr.substring(6, 8)); // parseInt로 앞의 0 제거
const day = cleanStr.substring(6, 8); // parseInt로 앞의 0 제거
return `${year}년 ${month}월 ${day}일`;
return `${year}-${month}-${day}`;
};
@@ -21,7 +21,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string]
if (/^\d{6}$/.test(dateStr)) {
const year = dateStr.substring(0, 4);
const month = parseInt(dateStr.substring(4, 6));
formattedData[fieldId] = `${year}년 ${month}월`;
formattedData[fieldId] = `${year}-${month}`;
} else {
formattedData[fieldId] = String(value);
}
@@ -34,7 +34,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string]
// yyyy-mm-dd hh:mm:ss 형식 확인
const dateTimeRegex = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/;
const match = dateTimeStr.match(dateTimeRegex);
if (match && match.length >= 6) {
const year = match[1];
const month = match[2];
@@ -42,12 +42,13 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string]
const hour = match[4];
const minute = match[5];
const second = match[6];
// 🎯 undefined 체크 후 parseInt 적용
if (year && month && day && hour && minute) {
const formattedMonth = parseInt(month);
const formattedDay = parseInt(day);
formattedData[fieldId] = `${year}년 ${formattedMonth}월 ${formattedDay}일 ${hour}시 ${minute}분 ${second}초`;
// const formattedMonth = parseInt(month);
// const formattedDay = parseInt(day);
// formattedData[fieldId] = `${year}-${formattedMonth}-${formattedDay} ${hour}:${minute}:${second}`;
formattedData[fieldId] = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
} else {
formattedData[fieldId] = String(value);
}
@@ -60,7 +61,7 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string]
// 🎯 기존 날짜 포맷팅 (yyyymmdd -> yyyy년 m월 d일)
if (dateFields.includes(fieldId)) {
const dateStr = String(value).trim();
// 숫자 타입이거나 숫자로만 구성된 문자열인지 확인
if (/^\d+$/.test(dateStr)) {
formattedData[fieldId] = formatDateString(dateStr);
@@ -74,7 +75,12 @@ export const formatDisplayData = (data: { [key: string]: any }): { [key: string]
if (numberFields.includes(fieldId)) {
const numValue = Number(value);
if (!isNaN(numValue) && isFinite(numValue)) {
formattedData[fieldId] = numValue.toLocaleString('ko-KR');
// 소수점이 있는 경우 4자리까지 표시
if (!Number.isInteger(numValue)) {
formattedData[fieldId] = numValue.toLocaleString('ko-KR', { maximumFractionDigits: 4 });
} else {
formattedData[fieldId] = numValue.toLocaleString('ko-KR');
}
} else {
formattedData[fieldId] = String(value);
}
@@ -1,164 +0,0 @@
import React, { useMemo } from "react";
import { SelectBox } from '@common/business_components/ui'
import { Label } from '@common/components';
interface Option {
id: number;
name: string;
}
interface SidoSigunguSelectBoxProps {
sido: number;
sigungu: number;
onSidoChange: (id: number) => void;
onSigunguChange: (id: number) => void;
className?: string;
}
const sidoList: { id: number, name: string }[] = [
{ id: 0, name: '전체' },
{ id: 26, name: '부산광역시' },
{ id: 28, name: '인천광역시' },
{ id: 30, name: '대전광역시' },
{ id: 41, name: '경기도' },
{ id: 42, name: '강원도' },
{ id: 44, name: '충청남도' },
{ id: 45, name: '전라북도' },
{ id: 46, name: '전라남도' },
{ id: 47, name: '경상북도' },
{ id: 48, name: '경상남도' },
];
const sigunguMap: Record<number, { id: number; name: string }[]> = {
26: [
{ id: 0, name: '전체' },
// ...부산산광역시의 시군구 추가
],
28: [
{ id: 0, name: '전체' },
// ...인천광역시의 시군구 추가
],
30: [
{ id: 0, name: '전체' },
// ...대전광역시의 시군구 추가
],
41: [
{ id: 0, name: '전체' },
{ id: 41820, name: '가평군' },
{ id: 41550, name: '안성시' },
{ id: 41830, name: '양평군' },
{ id: 41670, name: '여주시' },
{ id: 41500, name: '이천시' },
{ id: 41430, name: '의왕시' },
{ id: 41150, name: '의정부시' },
{ id: 41480, name: '파주시' },
{ id: 41650, name: '포천시' },
// ...경기도의 시군구 추가
],
42: [
{ id: 0, name: '전체' },
{ id: 42170, name: '동해시' },
{ id: 42110, name: '춘천시' },
{ id: 42720, name: '홍천군' },
// ...강원도의 시군구 추가
],
44: [
{ id: 0, name: '전체' },
{ id: 44770, name: '서천군' },
// ...충청남도의 시군구 추가
],
45: [
{ id: 0, name: '전체' },
{ id: 45130, name: '군산시' },
{ id: 45190, name: '남원시' },
{ id: 45730, name: '무주군' },
{ id: 45800, name: '부안군' },
{ id: 45770, name: '순창군' },
{ id: 45140, name: '익산시' },
{ id: 45750, name: '임실군' },
{ id: 45710, name: '완주군' },
{ id: 45110, name: '전주시' },
// ...전라북도의 시군구 추가
],
46: [
{ id: 0, name: '전체' },
{ id: 46810, name: "강진군" },
{ id: 46770, name: "고흥군" },
{ id: 46720, name: "곡성군" },
{ id: 46730, name: "구례군" },
{ id: 46230, name: "광양시" },
{ id: 46170, name: "나주시" },
{ id: 46110, name: "목포시" },
{ id: 46840, name: "무안군" },
{ id: 46150, name: "순천시" },
{ id: 46910, name: "신안군" },
{ id: 46130, name: "여수시" },
{ id: 46890, name: "완도군" },
// ...전라남도의 시군구 추가
],
47: [
{ id: 0, name: '전체' },
{ id: 47130, name: "경주시" },
{ id: 47830, name: "고령군" },
{ id: 47190, name: "구미시" },
{ id: 47720, name: "군위군" },
{ id: 47150, name: "김천시" },
{ id: 47280, name: "문경시" },
{ id: 47920, name: "봉화군" },
{ id: 47250, name: "상주시" },
{ id: 47770, name: "영덕군" },
{ id: 47760, name: "영양군" },
{ id: 47210, name: "영주시" },
{ id: 47900, name: "예천군" },
{ id: 47930, name: "울진군" },
{ id: 47730, name: "의성군" },
{ id: 47750, name: "청송군" },
{ id: 47850, name: "칠곡군" },
{ id: 47110, name: "포항시" },
// ...경상북도의 시군구 추가
],
48: [
{ id: 0, name: '전체' },
{ id: 48310, name: "거제시" },
{ id: 48250, name: "김해시" },
{ id: 48270, name: "밀양시" },
{ id: 48220, name: "통영시" },
// ...경상남도의 시군구 추가
],
// ...나머지 시도별 시군구 추가
};
export function SidoSigunguSelectBox({
sido,
sigungu,
onSidoChange,
onSigunguChange,
className = "",
}: SidoSigunguSelectBoxProps) {
// 선택된 시도에 따라 시군구 리스트를 가져옴
const sigunguList = useMemo(() => sigunguMap[sido] || [{ id: 0, name: '전체' }], [sido]);
return (
<div className={`flex items-center justify-center ${className} mr-8`}>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
<SelectBox
className="w-60 mr-8"
selectClass="rounded-lg bg-white"
label=""
selectArray={sidoList.map(opt => ({ val: opt.id, text: opt.name }))}
value={sido}
onSelectValue={val => onSidoChange(Number(val))}
selectContentClass="z-[101]"
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
<SelectBox
className="w-48"
selectClass="w-48 rounded-lg bg-white truncate"
label=""
selectArray={sigunguList.map(opt => ({ val: opt.id, text: opt.name }))}
value={sigungu}
onSelectValue={val => onSigunguChange(Number(val))}
selectContentClass="z-[101]"
/>
</div>
);
}
@@ -0,0 +1,159 @@
import React, { useState, useMemo, useEffect } from "react";
import { SelectBox } from '@common/business_components/ui'
import { Label } from '@common/components';
import useStore from '../../store/useStore';
import { getData } from '@/components/lib/api/index';
interface ApiSelectOption {
text: string;
val: string;
}
interface CtpvSggPrcsFcltSelectBoxProps {
sido: number;
sigungu: number;
sggName?: string;
onSidoChange: (id: number) => void;
onSigunguChange: (id: number) => void;
setCtpvName?: (name: string) => void;
setSggName?: (name: string) => void;
className?: string;
selectedSwagePrcsFclt?: string | number;
onSwagePrcsFcltChange?: (value: string | number) => void;
setIsLoading?: (isLoading: boolean) => void;
}
const defaultOption: ApiSelectOption = { val: '0', text: '전체' };
export function CtpvSggPrcsFcltSelectBox({
sido,
sigungu,
sggName,
onSidoChange,
setCtpvName,
onSigunguChange,
setSggName,
className = "",
selectedSwagePrcsFclt,
onSwagePrcsFcltChange,
setIsLoading,
}: CtpvSggPrcsFcltSelectBoxProps) {
const [prcsFcltOptions, setPrcsFcltOptions] = useState<ApiSelectOption[]>([]);
const { ctpv, sgg } = useStore();
useEffect(() => {
(async () => {
onSidoChange(0);
onSigunguChange(0);
if (setCtpvName && setSggName) {
setCtpvName("");
setSggName("");
}
})();
}, []);
useEffect(() => {
onSwagePrcsFcltChange?.('0');
const fetchData = async () => {
if (!sggName) {
console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sggName);
setPrcsFcltOptions([]);
if (setIsLoading) setIsLoading(false);
return;
}
if (setIsLoading) setIsLoading(true);
try {
const queryParamsFacility = new URLSearchParams({
lyrTypeNm: 'tb_il_swage_prcs_fclt',
page: '0',
size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능
sort: 'string',
});
const cqlFiltersFacility: string[] = [];
if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`);
if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and '));
console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
const facilityRes = await getData(`v1/fclts?${queryParamsFacility.toString()}`);
console.log('facilityRes.data', facilityRes.data);
const facilities = (facilityRes.data || [])
.map((item: any) => ({
text: item.swtrmtFcltNm,
val: item.fid
}))
.sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko'));
setPrcsFcltOptions(facilities);
} catch (error) {
console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
setPrcsFcltOptions([]);
} finally {
if (setIsLoading) setIsLoading(false);
}
};
fetchData();
}, [sggName]);
return (
<div className={`flex items-center justify-center ${className} mr-8`}>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
<SelectBox
className="w-60 mr-8"
selectClass="rounded-lg bg-white"
label=""
selectArray={
[defaultOption,
...ctpv.map(opt => ({ val: opt.id, text: opt.name }))
]
}
value={sido}
onSelectValue={val => {
onSidoChange(Number(val));
if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || "");
}}
selectContentClass="z-[101]"
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
<SelectBox
className="w-48"
selectClass="w-48 rounded-lg bg-white truncate"
label=""
selectArray={
[defaultOption,
...sgg
.filter(opt => String(opt.id).slice(0, 2) === String(sido))
.map(opt => ({ val: opt.id, text: opt.name }))
]
}
value={sigungu}
onSelectValue={val => {
onSigunguChange(Number(val))
if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || "");
}}
selectContentClass="z-[101]"
/>
{selectedSwagePrcsFclt && onSwagePrcsFcltChange && (
<>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap ml-8 mr-4">하수처리시설명</Label>
<SelectBox
className="w-[160px]"
selectClass="rounded-lg bg-white truncate"
label=""
selectArray={[defaultOption, ...prcsFcltOptions]}
value={selectedSwagePrcsFclt}
onSelectValue={(val) => onSwagePrcsFcltChange?.(val)}
selectContentClass="z-[101]"
/>
</>
)}
</div>
);
}
+56
View File
@@ -0,0 +1,56 @@
import { getCmsData } from "@/components/lib/api";
export interface Option { id: number, name: string }
export type BIZ_CD_TYPE = {
lwpoCd: string,
upCd: string,
lwpoCdNm: string,
unit: string,
prntLwpoCd: null
};
export const fetchCtpvSggData = async (set: (state: any) => void) => {
try {
const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD/Y');
const ctpvList: Option[] = ctpvData
.filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2)
.map((feature: any) => ({
id: feature.lwpoCd,
name: feature.lwpoCdNm
}));
const sggList: Option[] = ctpvData
.filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2)
.map((item: any) => ({
id: item.lwpoCd,
name: item.lwpoCdNm
}));
set({ ctpv: ctpvList, sgg: sggList });
} catch (e: any) {
// alert("시도, 시군구 데이터 로드 실패: " + e.message);
console.error("CTPV 데이터 로드 실패:", e);
}
};
export const fetchBIZ_SE_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZSECD`);
set({ BIZ_SE_CD: res });
} catch (e: any) {
// alert("BIZ_SE_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_SE_CD 데이터 로드 실패:", e);
}
};
export const fetchBIZ_TY_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`);
set({ BIZ_TY_CD: res });
} catch (e: any) {
// alert("BIZ_TY_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_TY_CD 데이터 로드 실패:", e);
}
};
+35
View File
@@ -0,0 +1,35 @@
import { create } from "zustand";
import { getGeoData, getCmsData } from "@/components/lib/api";
import { fetchCtpvSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index";
interface Option { id: number, name: string }
export type BIZ_CD_TYPE = {
lwpoCd: string,
upCd: string,
lwpoCdNm: string,
unit: string,
prntLwpoCd: null
};
interface SnStoreState {
ctpv: Option[];
sgg: Option[];
BIZ_SE_CD: BIZ_CD_TYPE[];
BIZ_TY_CD: BIZ_CD_TYPE[];
fetchCtpvSggData: () => Promise<void>;
fetchBIZ_SE_CD: () => Promise<void>;
fetchBIZ_TY_CD: () => Promise<void>;
}
const useStore = create<SnStoreState>((set) => ({
ctpv: [],
sgg: [],
BIZ_SE_CD: [],
BIZ_TY_CD: [],
fetchCtpvSggData: () => fetchCtpvSggData(set),
fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set),
fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set),
}));
export default useStore;
Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

+1
View File
@@ -2,5 +2,6 @@ export { useMutationWrapper } from "./use-mutation-wrapper";
export { useAlertStore } from "./use-alert-store";
export { useMenuStore } from "./use-menu-store";
export { useUserStore } from "./use-user-store";
export { useDataStore } from "./use-data-store";
export { usePrompt } from "./use-prompt";
export { getCookie, setCookie, removeCookie } from "./cookie";
+14
View File
@@ -0,0 +1,14 @@
import { useStore } from "zustand";
import { useContext } from "react";
import type { DataStore } from "../store/data-store";
import { StoreContext } from "../providers/store-provider";
export const useDataStore = <T>(selector: (store: DataStore) => T): T => {
const storeContext = useContext(StoreContext);
if (!storeContext) {
throw new Error("useDataStore must be used within StoreProvider");
}
return useStore(storeContext.dataStore, selector);
};
@@ -6,6 +6,7 @@ import {
createAttachmentStore,
createMenuStore,
createUserStore,
createDataStore,
} from "../store";
type StoreContextType = {
@@ -13,6 +14,7 @@ type StoreContextType = {
attachmentStore: ReturnType<typeof createAttachmentStore>;
menuStore: ReturnType<typeof createMenuStore>;
userStore: ReturnType<typeof createUserStore>;
dataStore: ReturnType<typeof createDataStore>;
};
export const StoreContext = createContext<StoreContextType | undefined>(
@@ -27,6 +29,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
attachmentStore: createAttachmentStore(),
menuStore: createMenuStore(),
userStore: createUserStore(),
dataStore: createDataStore(),
};
}
+90
View File
@@ -0,0 +1,90 @@
import { create } from "zustand";
import { getCmsData } from "@/components/lib/api";
export interface Option { id: number, name: string }
export type BIZ_CD_TYPE = {
lwpoCd: string,
upCd: string,
lwpoCdNm: string,
unit: string,
prntLwpoCd: null
};
export type DataState = {
ctpv: Option[];
sgg: Option[];
BIZ_SE_CD: BIZ_CD_TYPE[];
BIZ_TY_CD: BIZ_CD_TYPE[];
}
export type FetchData = {
fetchCtpvSggData: () => Promise<void>;
fetchBIZ_SE_CD: () => Promise<void>;
fetchBIZ_TY_CD: () => Promise<void>;
};
export type DataStore = DataState & FetchData;
export const defaultInitState: DataState = {
ctpv: [],
sgg: [],
BIZ_SE_CD: [],
BIZ_TY_CD: [],
};
const fetchCtpvSggData = async (set: (state: any) => void) => {
try {
const ctpvData = await getCmsData('/api/v1/code/cache/ADSTDSTRCD');
console.log("CTPV Data:", ctpvData);
const ctpvList: Option[] = ctpvData
.filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length === 2)
.map((feature: any) => ({
id: feature.lwpoCd,
name: feature.lwpoCdNm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
const sggList: Option[] = ctpvData
.filter((item: any) => typeof item.lwpoCd === 'string' && item.lwpoCd.length !== 2)
.map((item: any) => ({
id: item.lwpoCd,
name: item.lwpoCdNm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
set({ ctpv: ctpvList, sgg: sggList });
} catch (e: any) {
// alert("시도, 시군구 데이터 로드 실패: " + e.message);
console.error("CTPV 데이터 로드 실패:", e);
}
};
const fetchBIZ_SE_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZSECD`);
set({ BIZ_SE_CD: res });
} catch (e: any) {
// alert("BIZ_SE_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_SE_CD 데이터 로드 실패:", e);
}
};
const fetchBIZ_TY_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`);
set({ BIZ_TY_CD: res });
} catch (e: any) {
// alert("BIZ_TY_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_TY_CD 데이터 로드 실패:", e);
}
};
export const createDataStore = (initState: DataState = defaultInitState) => {
return create<DataStore>((set) => ({
...initState,
fetchCtpvSggData: () => fetchCtpvSggData(set),
fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set),
fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set),
}));
};
+1
View File
@@ -2,3 +2,4 @@ export { createAlertStore } from "./alert-store";
export { createAttachmentStore } from "./attachment-store";
export { createMenuStore, type BbsType } from "./menu-store";
export { createUserStore, type IUserInfo } from "./user-store";
export { createDataStore, type DataStore } from "./data-store";
+2
View File
@@ -10,6 +10,8 @@ NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
@@ -0,0 +1,89 @@
import { useWrStore } from "@/app/store";
import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { tableData } from "@/components/lib/func/commom";
import { Title, UiTable } from "@common/business_components/ui";
import { useEffect, useState } from "react";
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
onFeatureSelect: (feature: any) => void;
onClose: () => void;
isRuseReg: { data: any | undefined };
}
const HasuFacility = ({ seletectedLayerNm, viewMode, onClose, onFeatureSelect, isRuseReg }: Props) => {
const {
code,
pbnst,
pubHasuInit,
} = useWrStore((state) => state);
const { pubFacHeadList, pbnstHasuRes, pbnstHasuResCount } = pbnst;
const [hasuTable, setHasuTable] = useState<pubHasuType[]>();
const [hasuPage, setHasuPage] = useState<number>(1);
useEffect(() => {
if (code.sggCd.length && pbnstHasuRes.length > 0) {
setHasuTable(tableData(pbnstHasuRes, { code }));
}
}, [code, pbnstHasuRes]);
useEffect(() => {
if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return;
if (!pbnst.isInit) {
pubHasuInit("1", "0");
}
}, [pbnst.isInit, pubHasuInit]);
useEffect(() => {
if (seletectedLayerNm !== '하수처리시설' || viewMode != 'list') return;
pubHasuInit(String(hasuPage), "0");
}, [hasuPage, pubHasuInit]);
const hasuCellClick = (index: number | undefined) => {
if (index !== undefined && pbnstHasuRes !== undefined) {
onFeatureSelect(pbnstHasuRes[index]);
onClose();
}
};
return (viewMode == 'list' ? (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center mb-4">
<Title title="하수처리시설 현황" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={hasuTable || []}
headlist={pubFacHeadList}
pageSize={11}
total={pbnstHasuResCount || 0}
headName={""}
cellClick={hasuCellClick}
onPageChange={setHasuPage}
/>
</div>
) :
(
<div style={{ overflowX: 'hidden' }}>
<div className="flex justify-between items-center mb-4">
<Title title="하수처리시설 상세정보" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={isRuseReg?.data || []}
headlist={pubFacHeadList.slice(0, 4)}
pageSize={1}
total={0}
headName={"위치정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
<UiTable
tableData={isRuseReg?.data || []}
headlist={pubFacHeadList.slice(4)}
pageSize={1}
total={0}
headName={"기본정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
</div>
)
)
}
export default HasuFacility;
@@ -0,0 +1,83 @@
import { useWrStore } from '@/app/store';
import type { customHtdflBdst } from '@/app/store/ruseFcltMng/fcltReg/htdflReg';
import { tableData } from '@/components/lib/func/commom';
import SttnDetailModal from '@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal';
import { Title, UiTable } from '@common/business_components/ui';
import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan';
import React, { useEffect, useState } from 'react'
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
onFeatureSelect: (feature: any) => void;
onClose: () => void;
htdflDetailModal: { isOpen: boolean; data: customHtdflBdst | undefined };
}
const HtdflFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, htdflDetailModal }: Props) => {
const { code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state);
const { htdflSttnRes, htdflSttnResCount } = htdflSttn;
const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>();
const [htdflPage, setHtdflPage] = useState(1)
// 온배수시설 cellClick 이벤트 (상세정보)
const htdflCellClick = (index: number | undefined) => {
if (index !== undefined && htdflTable !== undefined) {
onFeatureSelect(htdflTable[index]);
onClose();
}
};
useEffect(() => {
if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return;
if (!htdflSttn.isInit) {
initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]);
useEffect(() => {
if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return;
const data = tableData(htdflSttnRes, { code });
const chData = data
.map((item: any) => ({
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].htdflObj,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
}))
.filter((item: any) => item.htdflObj !== undefined);
setHtdflTable(chData);
}, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]);
useEffect(() => {
if (seletectedLayerNm !== '온배수시설' || viewMode != 'list') return;
htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]);
return (
viewMode == 'detail' ? (
<SttnDetailModal type={"HTB"} formObj={htdflDetailModal.data as customHtdflBdst} />
) : (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="온배수시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={htdflTable || []}
headlist={htdflSttn.htdflSttHeadList}
pageSize={20}
total={htdflSttnResCount || 0}
cellClick={(index: number | undefined) => {
htdflCellClick(index ?? undefined);
}}
onPageChange={setHtdflPage}
>
<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />
</UiTable>
</div>
)
)
}
export default HtdflFacility
@@ -0,0 +1,105 @@
import { useWrStore } from "@/app/store";
import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { tableData } from "@/components/lib/func/commom";
import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal";
import { Title, UiTable } from "@common/business_components/ui";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import { useEffect, useState } from "react";
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
onFeatureSelect: (feature: any) => void;
onClose: () => void;
rainDetailModal: { isOpen: boolean; data: customRainBdst | undefined };
}
const RainFacility = ({seletectedLayerNm,viewMode,onFeatureSelect,onClose,rainDetailModal}:Props) => {
// 빗물재이용시설 변수
const { code, rainSttn, rainSttnSearch, rainSttnInit } = useWrStore(
(state) => state
);
const { rainSttnRes, rainSttnResCount } = rainSttn;
const [rainTable, setRainTable] = useState<customRainBdst[]>();
const [rainPage, setRainPage] = useState(1)
// 빗물재이용시설 cellClick 이벤트 (상세정보)
const rainCellClick = (index: number | undefined, headId: string) => {
if (
index !== undefined &&
rainTable !== undefined &&
headId !== 'chBuildingYn') {
onFeatureSelect(rainTable[index]);
onClose();
}
}
useEffect(() => {
if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return;
console.log('빗물이용시설 초기값 호출', viewMode)
if (!rainSttn.isInit) {
rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]);
useEffect(() => {
const data = tableData(rainSttnRes, { code });
const chData = data
.map((item: any) => {
return {
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].rainyObj,
dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.dtfcltCpct,
instlCst: item.reuseBuilding[0].instlCst === -1
? "확인불가"
: item.reuseBuilding[0].instlCst,
instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1
? "확인불가"
: item.reuseBuilding[0].instlCstSprtAmt,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.pntfColArea,
filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct,
archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea,
};
})
.filter((item: any) => item.rainyObj !== undefined);
setRainTable(chData);
}, [code, rainSttnRes, rainSttn.rainSttHeadList]);
useEffect(() => {
if (seletectedLayerNm !== '빗물이용시설' || viewMode != 'list') return;
rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]);
return (viewMode == 'detail' ?
(<SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} />)
: (
<>
<div className="flex justify-between items-center">
<Title title="빗물이용시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={rainTable || []}
headlist={rainSttn.rainSttHeadList}
pageSize={20}
total={rainSttnResCount}
cellClick={(index: number | undefined, headId: string) => {
rainCellClick(index ?? undefined, headId);
}}
onPageChange={setRainPage}
>
<HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} />
</UiTable>
</>
))
}
export default RainFacility;
@@ -0,0 +1,146 @@
import { useWrStore } from "@/app/store";
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type";
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
import { Title, UITab, UiTable } from "@common/business_components/ui";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import { useState, useEffect, useCallback } from "react";
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
setFcltPk: (pk: string) => void;
onFeatureSelect: (feature: any) => void;
onClose: () => void;
actTabData: { id: string; name: string; data: any } | undefined;
tabData: { id: string; name: string; data: any }[];
}
const ResueFacility = ({ viewMode, seletectedLayerNm, tabData, actTabData, setFcltPk, onFeatureSelect, onClose}: Props) => {
const [activeTab, setActiveTab] = useState<string>(''); // 현재 활성화된 탭
// 하수재이용시설 변수
const {
pbnstSttn,
pbnstSttnSearch,
pbnSttnInit,
getFlcts
} = useWrStore((state) => state);
const { fclts, pbnstSttnResCount } = pbnstSttn
const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>()
// 재이용시설 cellClick 이벤트 (상세정보)
const pbnstCellClick = (index: number | undefined) => {
if (
index !== undefined &&
pbnstSttn.pbnstSttnRes &&
pbnstSttn.pbnstSttnRes[index]
) {
setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId);
onFeatureSelect(pbnstSttn.pbnstSttnRes[index]);
onClose()
}
};
// 재이용 현황 초기값
useEffect(() => {
if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return;
if (!pbnstSttn.isInit) {
getFlcts()
}
}, [seletectedLayerNm, pbnstSttn.isInit]);
useEffect(() => {
if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return;
pbnSttnInit("1", "20", {
partValue: "A",
sidoValue: '0',
sigunValue: "0",
regionValue: "",
unitWaterShedValue: "",
middleRegionValue: "",
envPartValue: "",
sidoText: "",
sigunText: ""
}, String(new Date().getFullYear()), pbnstSttn.fclts);
}, [fclts, pbnSttnInit, pbnstSttn.fclts, seletectedLayerNm])
useEffect(() => {
setBdstTable(pbnstSttn.pbnstSttnRes);
}, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]);
// 재이용시설 조회
// 재이용시설 페이징처리 관련 함수
const onPageChangeHandler = useCallback((page: number) => {
if (seletectedLayerNm !== '하수재이용시설' || viewMode !='list') return;
pbnstSttnSearch(
false,
String(page),
String(20),
{
partValue: "A",
sidoValue: "0",
sigunValue: "0",
regionValue: "",
unitWaterShedValue: "",
middleRegionValue: "",
envPartValue: "",
sidoText: "",
sigunText: "",
},
"",
String(new Date().getFullYear()), fclts
);
}, [fclts, pbnstSttnSearch]);
// FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제.
// const excelClick = useCallback(() => {
// // if (codeData) pbnstSttnSearch(1, "10000", codeData);
// excelDownload(
// [[]],
// "하수재이용시설 현황",
// "sample_pbnstSttn.xlsx",
// "A1:U2"
// );
// }, [pbnstSttnSearch]);
return (
viewMode == 'detail' ?
// 재이용시설 상세
<>
<div className="flex justify-between items-center mb-4">
<Title title="하수재이용시설 상세정보" />
</div>
<div className="flex justify-between items-center mb-8">
<UITab
defaultValue={activeTab}
tabList={tabData.map((tab) => ({
value: tab.id,
label: tab.name,
onClick: () => setActiveTab(tab.id),
}))}
/>
</div>
<PbnstSttnModal
data={actTabData?.data[0] || ({} as customPbnst)}
titleVisible={false}
/>
</> :
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="하수재이용시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={bdstTable || []}
headlist={pbnstSttn.pbnstSttHeadList}
pageSize={20}
total={pbnstSttnResCount || 0}
cellClick={(index: number | undefined) => {
pbnstCellClick(index);
}}
onPageChange={onPageChangeHandler}
>
<HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} />
</UiTable>
</div>
)
}
export default ResueFacility;
@@ -0,0 +1,103 @@
import { useWrStore } from '@/app/store';
import { tableData, formatPhoneNumber } from '@/components/lib/func/commom';
import { Title, UiTable } from '@common/business_components/ui';
import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form';
import moment from 'moment';
import React, { useEffect, useState } from 'react'
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
onFeatureSelect: (feature: any) => void;
onClose: () => void;
isDmdModal: { data: any | undefined };
}
const ReuseMngFacility = ({seletectedLayerNm, viewMode, onFeatureSelect, onClose, isDmdModal}:Props) => {
const { code,
coSttn,
coSttnInit,
coSttnSearch,
} = useWrStore((state) => state);
const [mngTable, setMngTable] = useState<DemandCompType[]>();
const [mngPage, setMngPage] = useState(1);
const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn;
useEffect(() => {
if (seletectedLayerNm !== '수요처' || viewMode != 'list') return;
if (!coSttn.isInit) {
coSttnInit("1", "20", "0");
}
}, [coSttn.isInit, coSttnInit]);
const coMngCellClick = (index: number | undefined) => {
if (index !== undefined && mngTable !== undefined) {
onFeatureSelect(mngTable[index]);
onClose();
}
};
useEffect(() => {
if (seletectedLayerNm !== '수요처' || viewMode!='list') return;
if (coSttnRes) {
if (coSttnRes?.length > 0 && code.cityCd.length > 0) {
const data = tableData(coSttnRes, { code });
const chData = data.map((item: any) => ({
...item,
telno: formatPhoneNumber(item.telno || undefined),
regDt: moment(item.regDt).format("YYYY-MM-DD"),
dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}`
}));
// .filter((item: any) => item !== undefined);
setMngTable(chData);
}
}
}, [coSttnRes, code]);
useEffect(() => {
if (seletectedLayerNm !== '수요처' || viewMode!='list') return;
coSttnSearch(String(mngPage), "20", String(0));
}, [coSttnSearch, mngPage]);
return (
viewMode == 'detail'
? (
<div style={{ overflowX: 'hidden' }}>
<div className="flex justify-between items-center mb-4">
<Title title="수요처 상세정보" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={isDmdModal?.data || []}
headlist={coSttHeadList.slice(0, 3)}
pageSize={1}
total={0}
headName={"위치정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
<UiTable
tableData={isDmdModal?.data || []}
headlist={coSttHeadList.slice(3)}
pageSize={1}
total={0}
headName={"기본정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
</div>
) : (<div>
<div className="flex justify-between items-center">
<Title title="수요처 현황" />
</div>
<UiTable
headName={[]}
tableData={mngTable || []}
headlist={coSttHeadList}
pageSize={20}
total={coSttnResCount + 10 || 0}
cellClick={(index: number | undefined) => {
coMngCellClick(index);
// setUpdateData(coSttnRes[index as number]);
}}
onPageChange={(page) => {
setMngPage(page);
}}
></UiTable></div>)
)
}
export default ReuseMngFacility
@@ -0,0 +1,107 @@
import { useWrStore } from "@/app/store";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import { tableData } from "@/components/lib/func/commom";
import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal";
import { Title, UiTable } from "@common/business_components/ui";
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import { useEffect, useState } from "react";
interface Props {
seletectedLayerNm: string;
viewMode: 'list' | 'detail' | 'nothing';
onFeatureSelect: (feature: any) => void;
onClose: () => void;
rswtrDetailModal: { isOpen: boolean; data: customRswtBdst | undefined };
}
const RswtFacility = ({ seletectedLayerNm, viewMode, onFeatureSelect, onClose, rswtrDetailModal }: Props) => {
const { code, rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore(
(state) => state
);
const { rswtSttnRes, rswtSttnResCount } = rswtSttn;
const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>()
const [rswtrPage, setRswtrPage] = useState(1);
// 중수도시설 cellClick 이벤트 (상세정보)
const rswtCellClick = (index: number | undefined, headId: string) => {
if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) {
// setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] });
// setViewMode('detail')
onFeatureSelect(rswtrTable[index]);
onClose();
}
}
useEffect(() => {
if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return;
if (!rswtSttn.isInit) {
rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]);
useEffect(() => {
if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return;
const data = tableData(rswtSttnRes, { code });
const chData = data
.map((item: customRswtBdst) => {
return {
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0]?.rswtrObj,
chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N',
instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst,
instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt,
archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가'
}
})
.filter(
(item: customRswtBdst) =>
item.reuseBuilding[0]?.rswtrObj !== undefined
);
setRswtrTable(chData);
}, [code, rswtSttnRes]);
// FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제.
// const rswtSttnExcelDownload = useCallback(() => {
// if (rswtSttn.excelList.length > 0) {
// if (rswtSttn.excelList.length === 0) return;
// excelDownload(
// rswtSttn.excelList,
// "중수도시설 현황",
// "sample_rswtSttn.xlsx",
// "A1:W3",
// );
// }
// }, [rswtSttn.excelList])
useEffect(() => {
if (seletectedLayerNm !== '중수도시설' || viewMode != 'list') return;
rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]);
return (
viewMode == 'detail' ?
// 중수도시설 상세
(
<SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} />
) : (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="중수도시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={rswtrTable || []}
headlist={rswtSttn.rswtSttHeadList}
pageSize={20}
total={rswtSttnResCount}
cellClick={(index: number | undefined, headId: string) => {
rswtCellClick(index, headId);
}}
onPageChange={setRswtrPage}
>
<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />
</UiTable>
</div>
))
}
export default RswtFacility;
@@ -0,0 +1,183 @@
"use client"
import NewWindowPortal from '@/app/map/components/newWindowPortal';
import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
import type { pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import { useGisContext } from "@common/business_components/ui/map/Gis";
import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ResueFacility from './layerPopup/reuseFacility';
import { detainSttn, fetchHasuDetail, fetchpbnstDetail } from '../util';
import HasuFacility from './layerPopup/hasuFacility';
import RainFacility from './layerPopup/rainFacility';
import RswtFacility from './layerPopup/rswtFacility';
import HtdflFacility from './layerPopup/htdflFacility';
import ReuseMngFacility from './layerPopup/reuseMngFacility';
interface NewWindowPopupProps {
featureData: any;
seletectedLayerNm?: string
onFeatureSelect: (data: any) => void;
onClose: () => void;
}
const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
featureData,
seletectedLayerNm,
onFeatureSelect,
onClose
}) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]);
const [activeTab, setActiveTab] = useState<string>('0');
const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list');
const [fcltPk, setFcltPk] = useState<string>('');
// onClose 함수를 useCallback으로 메모이제이션
const memoizedOnClose = useCallback(() => {
onClose();
}, [onClose]);
// 빗물재이용시설 변수
const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false });
const [rainList, setRainList] = useState<customRainBdst[]>([]);
// 중수도시설 변수
const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false });
const [rswtrList, setRswtrList] = useState<customRswtBdst[]>([]);
// 온배수시설 변수
const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false });
const [htdflList, setHtdflList] = useState<customHtdflBdst[]>([]);
// 하수처리시설 변수
const [hasuList, setHasuList] = useState<pubHasuType[]>([]);
const [isRuseReg, setIsRuseReg] = useState<{
data: any | undefined;
}>({ data: undefined });
// 수요처관리 변수
const [isDmdModal, setIsDmdModal] = useState<{
isMoal: boolean;
data: any | undefined;
}>({ isMoal: false, data: undefined });
const [mngList, setMngList] = useState<DemandCompType[]>([]);
///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 ///////////////////////////////////////////////
const getSelectedLayers = () => {
console.trace('호출확인', 1)
let result = null;
const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: () => null };
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
featureLayer = layer; // 피처가 속한 레이어 찾기)
}
});
result = featureLayer.get('lyrNm');
return result;
};
// 상세 목록 데이터 상태 처리
useEffect(() => {
if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] });
}, [hasuList])
useEffect(() => {
if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] });
}, [rainList])
useEffect(() => {
if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] });
}, [rswtrList])
useEffect(() => {
if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] });
}, [htdflList])
useEffect(() => {
if (mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] });
}, [mngList])
const lastCalledIdRef = useRef<string | null>(null);
const [layerId, setLayerId] = useState<string | null>();
useEffect(() => {
const layers = getSelectedLayers();
setLayerId(layers);
}, []); // 최초 렌더링 시 호출
// 피쳐 데이터가 변경될 때마다 호출
useEffect(() => {
if (!featureData || typeof featureData.getProperties !== "function" || featureData.getProperties() == '') return;
const selectedProps = featureData.getProperties();
const currentId = featureData.getId?.() || selectedProps?.id || selectedProps?.fid;
// 유효하지 않은 ID는 아예 무시
if (!currentId || typeof currentId !== 'string') return;
// 이전과 동일하면 무시
if (!currentId || currentId === lastCalledIdRef.current) return;
lastCalledIdRef.current = currentId // 새로운 피처일 때만 API 호출
const layerId = getSelectedLayers?.();
if (!layerId || typeof layerId !== 'string') return;
const featureCd =
layerId === '하수재이용시설' ? selectedProps?.swtrmt_fclt_cd :
['빗물이용시설', '중수도시설', '온배수시설', '수요처'].includes(layerId) ? selectedProps?.bdst_nm :
layerId === '하수처리시설' ? selectedProps?.swtrmt_fclt_nm :
null;
if (!featureCd || featureCd === fcltPk) return;
if (layerId === '하수재이용시설') {
fetchpbnstDetail(featureCd, setTabData);
} else if (layerId === '하수처리시설') {
fetchHasuDetail(featureCd, selectedProps?.swtrmt_fclt_cd).then((data) => { setHasuList(data) })
} else {
detainSttn(featureCd, layerId).then((res) => {
if (layerId === '빗물이용시설') setRainList(res);
else if (layerId === '중수도시설') setRswtrList(res);
else if (layerId === '온배수시설') { setHtdflList(res); setHtdflDetailModal({ isOpen: true, data: res[0] }); }
else if (layerId === '수요처') setMngList(res);
});
}
setFcltPk(featureCd);
setActiveTab(featureCd);
setViewMode("detail");
}, [featureData]);
///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 ///////////////////////////////////////////////
// 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수
const renderConditionalContent = () => {
const actTabData = tabData.find((tab) => tab.id === activeTab);
if (layerId === null || layerId === undefined) {
setLayerId(seletectedLayerNm || '');
}
switch (layerId) {
case '하수재이용시설':
return (<ResueFacility viewMode={viewMode} seletectedLayerNm={layerId} tabData={tabData} actTabData={actTabData} setFcltPk={setFcltPk} onFeatureSelect={onFeatureSelect} onClose={onClose} />)
case '하수처리시설':
return (<HasuFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isRuseReg={isRuseReg} />)
case '빗물이용시설':
return (<RainFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rainDetailModal={rainDetailModal} />)
case '중수도시설':
return (<RswtFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} rswtrDetailModal={rswtrDetailModal} />)
case '온배수시설':
return (<HtdflFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} htdflDetailModal={htdflDetailModal} />)
case '수요처':
return(<ReuseMngFacility seletectedLayerNm={layerId} viewMode={viewMode} onFeatureSelect={onFeatureSelect} onClose={onClose} isDmdModal={isDmdModal} />)
default:
break;
}
return null; // 조건에 맞는 콘텐츠가 없을 경우
};
return (
viewMode == 'nothing' ? <></> :
<NewWindowPortal
onClose={memoizedOnClose}
title="시설 상세 정보"
scrollbars='yes'
viewMode={viewMode}
resizable='no'>
{/* 체크된 레이어에 따른 조건부 렌더링 */}
{renderConditionalContent()}
</NewWindowPortal>
);
};
export default NewWindowPopup;
@@ -31,9 +31,10 @@ interface NewWindowPortalProps {
height?: number;
resizable?: string;
scrollbars?: string;
viewMode?: 'list' | 'detail' | 'nothing';
}
const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", width = 900, height = 800, resizable = "yes", scrollbars = "yes" }) => {
const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "", resizable = "yes", scrollbars = "yes", viewMode }) => {
const [container, setContainer] = useState<HTMLElement | null>(null);
const newWindowRef = useRef<Window | null>(null);
const onCloseRef = useRef(onClose);
@@ -46,7 +47,15 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti
const handleUnload = useCallback(() => {
onCloseRef.current();
}, []);
useEffect(() => {
if (newWindowRef.current) {
if (viewMode == 'detail') {
newWindowRef.current.resizeTo(1100, 700);
} else {
newWindowRef.current.resizeTo(screen.availWidth, screen.availHeight);
}
}
}, [viewMode]);
useEffect(() => {
// 이미 창이 열려있으면 새로 열지 않음
if (newWindowRef.current && !newWindowRef.current.closed) return;
@@ -57,11 +66,11 @@ const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, ti
const screenHeight = window.innerHeight ?? document.documentElement.clientHeight;
// 중앙 위치 계산
const left = screenLeft + (screenWidth - width) / 2;
const top = screenTop + (screenHeight - height) / 2;
const left = screenLeft + (screenWidth - window.screen.width) / 2;
const top = screenTop + (screenHeight - window.screen.height) / 2;
// 1. 새 창을 열고 참조를 저장합니다.
const popupOptions = `width=${width},height=${height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`;
const popupOptions = `width=${window.screen.width},height=${window.screen.height},left=${left}, top=${top}, resizable=${resizable},scrollbars=${scrollbars}`;
newWindowRef.current = window.open('', title, popupOptions);
if (newWindowRef.current) {
+46 -30
View File
@@ -3,15 +3,16 @@
import Gis from "@common/business_components/ui/map/Gis";
import { useEffect, useRef, useState, useCallback } from "react";
import { mapService } from "../service";
import NewWindowPopup from "../newWindowPopup";
import NewWindowPopup from "../components/newWindowPopup";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { SearchBar } from "../searchBar/searchBar";
const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const gisMaps = useRef<any | null>(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
const [featureData, setFeatureData] = useState<any>(null); // 선택된 피쳐 데이터
const [checkedLayers, setCheckedLayers] = useState<string[]>([]);
const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]);
@@ -63,25 +64,26 @@ const Map = () => {
let foundFeature: any = null;
let featureId = data.fid;
const layerName = data.layerNm;
if (layerName === "tb_wr_bdst_ruse_fclt") {
console.log("선택된 레이어 이름:", data, layerName);
if (layerName === "하수재이용시설") {
// 재이용시설
featureId = `${layerName}.${data.searchPk}`;
} else if ( //빗물이용시설, 중수도시설
layerName === "tb_wr_rnwtr_utztn_fclt" ||
layerName == "tb_wr_rswtr_fclt"
} else if (
//빗물이용시설, 중수도시설
layerName === "빗물이용시설" ||
layerName == "중수도시설"
) {
featureId = `${layerName}.${data.bdstInfoSn}`;
} else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설
} else if (layerName === "하수처리시설") { //하수처리시설
featureId = `${layerName}.${data.bscFctCd}`;
} else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설
} else if (layerName === "온배수시설") { //온배수시설
featureId = `${layerName}.${data.bdstRuseFcltSn}`;
} else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처
} else if (layerName == "수요처") { //수요처
featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`;
} else {
console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName);
featureId = `${layerName}.${data.fid || data.id || data.ftrIdn}`;
}
map
.getLayers()
.getArray()
@@ -111,7 +113,9 @@ const Map = () => {
} else {
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
}
const currentId = data?.fid || data?.id || data?.ftrIdn;
const lastId = featureData?.fid || featureData?.id || featureData?.ftrIdn;
if (featureData && currentId === lastId) return; // 이전과 같으면 무시
setFeatureData(data);
setIsOpen(true);
setIsSearchModalOpen(false);
@@ -127,7 +131,18 @@ const Map = () => {
// />
// );
// };
const renderDetailView = useCallback(
(props: any) => (
<NewWindowPopup
key={featureData?.fid || featureData?.id || featureData?.ftrIdn}
onClose={handlePopupClose}
checkedLayers={checkedLayers}
checkedLayersData={checkedLayersData}
{...props}
/>
),
[checkedLayers, checkedLayersData] // 필요 시 의존성 넣기
);
return (
<div className="bg-container">
<Gis
@@ -136,24 +151,7 @@ const Map = () => {
gisMaps={gisMaps}
isMiniMap={false}
searchView={undefined}
// searchView={(props: any) => (
// <MapSearchBar
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// checkedLayers={checkedLayers}
// checkedLayersData={checkedLayersData}
// {...props}
// />
// )
// }
detailView={(props: any) => (
<NewWindowPopup
onClose={handlePopupClose} // 메모이제이션된 함수 사용
checkedLayers={checkedLayers}
checkedLayersData={checkedLayersData}
{...props}
/>
)}
detailView={renderDetailView}
onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
@@ -163,6 +161,24 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={() => {
const facilityTypes = [
{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
{ text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" },
{ text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" },
{ text: "중수도시설", val: "tb_wr_rswtr_fclt" },
{ text: "온배수시설", val: "tb_wr_htdfl_fclt" },
{ text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" },
{ text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" },
];
return (
<SearchBar
onFeatureSelect={onFeatureSelect}
facilityTypes={facilityTypes}
/>
);
}}
waterDepth={undefined}
/>
</div>
);
-795
View File
@@ -1,795 +0,0 @@
"use client"
import NewWindowPortal from '@/app/map/newWindowPortal';
import { useWrStore } from "@/app/store";
import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
import type { customPbnst, pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type { customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type";
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
import { formatPhoneNumber, tableData } from "@/components/lib/func/commom";
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal";
import { Title, UITab, UiTable } from '@common/business_components';
import { useGisContext } from "@common/business_components/ui/map/Gis";
import type { DemandCompType } from '@common/business_components/ui/sdmInsertForm/form';
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import moment from 'moment';
import React, { useCallback, useEffect, useState } from 'react';
interface NewWindowPopupProps {
featureData: any;
seletectedLayerNm?: string
viewMode?: 'list' | 'detail' | 'nothing'
onFeatureSelect: (data: any) => void;
onClose: () => void;
}
const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
featureData,
seletectedLayerNm,
onFeatureSelect,
onClose
}) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
const [tabData, setTabData] = useState<{ id: string; name: string; data: any }[]>([]);
const [activeTab, setActiveTab] = useState<string>('0');
const [viewMode, setViewMode] = useState<'list' | 'detail' | 'nothing'>('list');
const [fcltPk, setFcltPk] = useState<string>('');
// onClose 함수를 useCallback으로 메모이제이션
const memoizedOnClose = useCallback(() => {
onClose();
}, [onClose]);
// 하수재이용시설 변수
const {
code,
pbnstSttn,
pbnstSttnSearch,
pbnSttnInit,
getFlcts
} = useWrStore((state) => state);
const { fclts, pbnstSttnResCount } = pbnstSttn
const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>()
// 빗물재이용시설 변수
const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore(
(state) => state
);
const { rainSttnRes, rainSttnResCount } = rainSttn;
const [rainTable, setRainTable] = useState<customRainBdst[]>();
const [rainPage, setRainPage] = useState(1)
const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false });
const [rainList, setRainList] = useState<customRainBdst[]>(rainSttnRes || []);
// 중수도시설 변수
const { rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore(
(state) => state
);
const { rswtSttnRes, rswtSttnResCount } = rswtSttn;
const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>();
const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false });
const [rswtrPage, setRswtrPage] = useState(1);
const [rswtrList, setRswtrList] = useState<customRswtBdst[]>(rswtSttnRes || []);
// 온배수시설 변수
const { htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state);
const { htdflSttnRes, htdflSttnResCount } = htdflSttn;
const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>();
const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false });
const [htdflPage, setHtdflPage] = useState(1)
const [htdflList, setHtdflList] = useState<customHtdflBdst[]>(htdflSttnRes || []);
//하수처리시설 변수
const {
pbnst,
pubHasuInit,
} = useWrStore((state) => state);
const { pubFacHeadList, pbnstHasuRes } = pbnst;
const [hasuList, setHasuList] = useState<pubHasuType[]>(pbnstHasuRes || []);
const [isRuseReg, setIsRuseReg] = useState<{
data: any | undefined;
}>({ data: undefined });
const [hasuTable, setHasuTable] = useState<pubHasuType[]>();
const [hasuPage, setHasuPage] = useState<number>(1);
// 수요처관리 변수
const {
coSttn,
coSttnInit,
coSttnSearch,
// getDemandCompList,
// regDemandCompany,
// updateDemandCompany,
// coSttnTxtSearch,
// demandCompList,
} = useWrStore((state) => state);
const [mngTable, setMngTable] = useState<DemandCompType[]>();
const [mngPage, setMngPage] = useState(1);
const { coSttnRes, coSttnResCount, coSttHeadList } = coSttn;
const [isDmdModal, setIsDmdModal] = useState<{
isMoal: boolean;
data: any | undefined;
}>({ isMoal: false, data: undefined});
const [mngList, setMngList] = useState<DemandCompType[]>(coSttnRes || []);
///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 ///////////////////////////////////////////////
const getSelectedLayers = () => {
let result = null;
const layers = gisMap ? gisMap.getLayers().getArray().filter((l: any) => l.getVisible()) : []; // gisMap이 null인지 확인 후 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: () => null };
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
featureLayer = layer; // 피처가 속한 레이어 찾기)
}
});
result = featureLayer.get('lyrNm');
return result;
};
const fetchpbnstDetail = async (featureCd: string) => {
try {
const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`);
// 중복 데이터 추가 방지
setTabDataFunc(response, featureCd);
} catch (error) {
console.error("Error fetching data:", error);
}
};
const fetchHasuDetail = async (featureCd: string, data: string) => {
try {
const rainRes = await postFectch(`/prcsFclt/ruseInfo/1/10/A/0/0/1/SW/${featureCd}`, [data], fetchType.COMMON)
setHasuList(
rainRes.data.map((item: any) => {
return {
...item,
fctCpc: Number(item.fctCpc) || 0,
ruse: item.ruse === false ? "N" : "Y",
};
})
)
} catch (error) {
console.error("Error fetching data:", error);
}
};
const detainSttn = async (featureCd: string, layerNm: string) => {
if (layerNm == 'tb_wr_rnwtr_utztn_fclt') { //빗물이용시설
const res = await getFetch(fetchType.WR, `/reuseFc/rainyInfoList/1/20/0/${featureCd}`)
setRainList(res)
}
else if (layerNm == 'tb_wr_rswtr_fclt') { //중수도 시설
const res = await getFetch(fetchType.WR, `/reuseFc/waterreuseFc/1/20/0/${featureCd}`)
setRswtrList(res)
}
else if(layerNm =='tb_wr_ruse_fclt_dmd_co'){ //수요처
const res = await getFetch(fetchType.WR, `/reuseFc/request/1/20/0/${featureCd}`)
setMngList(res)
}
else {
const res = await getFetch(fetchType.WR, `/drain/facilityList/1/20/0/${featureCd}`)
setHtdflList(res)
setHtdflDetailModal({ isOpen: true, data: htdflSttnRes[0] });
}
}
// 상세 목록 데이터 상태 처리
useEffect(() => {
if (hasuList.length > 0) setIsRuseReg({ data: [hasuList[0]] });
}, [hasuList])
useEffect(() => {
if (rainList.length > 0) setRainDetailModal({ isOpen: true, data: rainList[0] });
}, [rainList])
useEffect(() => {
if (rswtrList.length > 0) setRswtrDetailModal({ isOpen: true, data: rswtrList[0] });
}, [rswtrList])
useEffect(() => {
if (htdflList.length > 0) setHtdflDetailModal({ isOpen: true, data: htdflList[0] });
}, [htdflList])
useEffect(() => {
if(mngList.length > 0) setIsDmdModal({ isMoal: true, data: [mngList[0]] });
},[mngList])
const layerId = getSelectedLayers();
if (featureData && typeof featureData.getProperties === "function") {
const selectedVal = featureData.getProperties();
let featureCd = layerId == '하수재이용시설' ? selectedVal?.swtrmt_fclt_cd : layerId == '빗물이용시설' || layerId == '중수도시설' || layerId == '온배수시설' || layerId == '수요처' ? selectedVal?.bdst_nm : layerId == '하수처리시설' ? selectedVal?.swtrmt_fclt_nm : null;
if (featureCd && featureCd !== fcltPk) {
if (layerId == '하수재이용시설') {
featureCd = selectedVal?.swtrmt_fclt_cd;
setFcltPk(featureCd); // 현재 호출된 featureCd 저장
setActiveTab(featureCd)
fetchpbnstDetail(featureCd); // 상세정보
}
else if (layerId == '하수처리시설') {
featureCd = selectedVal?.swtrmt_fclt_nm;
setFcltPk(featureCd); // 현재 호출된 featureCd 저장
fetchHasuDetail(featureCd, selectedVal?.swtrmt_fclt_cd);
}
else {
featureCd = selectedVal?.bdst_nm;
setFcltPk(featureCd)
if (layerId) {
detainSttn(featureCd, layerId);
}
}
setViewMode("detail")
}
} else if (layerId === null && seletectedLayerNm) { setViewMode("list") }
///////////////////////////////////////////// 피쳐데이터 선택 시 각 시설별 데이터 처리 ///////////////////////////////////////////////
///////////////////////////////////////////// 하수재이용시설 //////////////////////////////////////////////////
// 재이용시설 cellClick 이벤트 (상세정보)
const pbnstCellClick = (index: number | undefined) => {
if (
index !== undefined &&
pbnstSttn.pbnstSttnRes &&
pbnstSttn.pbnstSttnRes[index]
) {
setFcltPk(pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltId);
onFeatureSelect(pbnstSttn.pbnstSttnRes[index]);
onClose()
}
};
// 재이용 현황 초기값
useEffect(() => {
if (!pbnstSttn.isInit) {
getFlcts()
}
}, [seletectedLayerNm, pbnstSttn.isInit, getFlcts]);
useEffect(() => {
pbnSttnInit("1", "15", {
partValue: "A",
sidoValue: '0',
sigunValue: "0",
regionValue: "",
unitWaterShedValue: "",
middleRegionValue: "",
envPartValue: "",
sidoText: "",
sigunText: ""
}, String(new Date().getFullYear()), pbnstSttn.fclts);
}, [fclts, pbnSttnInit, pbnstSttn.fclts])
useEffect(() => {
setBdstTable(pbnstSttn.pbnstSttnRes);
}, [seletectedLayerNm, pbnstSttn.pbnstSttnRes]);
// 재이용시설 상세정보
const setTabDataFunc = (res: any, pk: string) => {
setTabData((prev) => {
if (prev.some((tab) => tab.id === pk)) {
return prev; // 이미 존재하는 탭이면 추가하지 않음
}
return [...prev, { id: pk, name: `재이용시설 ${prev.length + 1}`, data: res }];
});
}
// 재이용시설 조회
// 재이용시설 페이징처리 관련 함수
const onPageChangeHandler = useCallback((page: number) => {
pbnstSttnSearch(
false,
String(page),
String(13),
{
partValue: "A",
sidoValue: "0",
sigunValue: "0",
regionValue: "",
unitWaterShedValue: "",
middleRegionValue: "",
envPartValue: "",
sidoText: "",
sigunText: "",
},
"",
String(new Date().getFullYear()), fclts
);
}, [fclts, pbnstSttnSearch]);
// FIXME: 윤선화차장님 요구사항으로 엑셀 다운로드 삭제.
// const excelClick = useCallback(() => {
// // if (codeData) pbnstSttnSearch(1, "10000", codeData);
// excelDownload(
// [[]],
// "하수재이용시설 현황",
// "sample_pbnstSttn.xlsx",
// "A1:U2"
// );
// }, [pbnstSttnSearch]);
///////////////////////////////////////////// 하수재이용시설 //////////////////////////////////////////////////
///////////////////////////////////////////// 빗물 재이용 //////////////////////////////////////////////////
// 빗물재이용시설 cellClick 이벤트 (상세정보)
const rainCellClick = (index: number | undefined, headId: string) => {
if (
index !== undefined &&
rainTable !== undefined &&
headId !== 'chBuildingYn') {
// setRainDetailModal({ isOpen: true, data: rainTable[index] })
// setViewMode('detail')
onFeatureSelect(rainTable[index]);
onClose();
}
}
useEffect(() => {
if (!rainSttn.isInit) {
rainSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, rainSttn.isInit, rainSttnInit]);
useEffect(() => {
const data = tableData(rainSttnRes, { code });
const chData = data
.map((item: any) => {
return {
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].rainyObj,
dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.dtfcltCpct,
instlCst: item.reuseBuilding[0].instlCst === -1
? "확인불가"
: item.reuseBuilding[0].instlCst,
instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1
? "확인불가"
: item.reuseBuilding[0].instlCstSprtAmt,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.pntfColArea,
filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1
? "확인불가"
: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct,
archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea,
};
})
.filter((item: any) => item.rainyObj !== undefined);
setRainTable(chData);
}, [code, rainSttnRes, rainSttn.rainSttHeadList]);
useEffect(() => {
rainSttnSearch(false, rainPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, rainPage, rainSttnSearch]);
///////////////////////////////////////////// 빗물 재이용 //////////////////////////////////////////////////
///////////////////////////////////////////// 중수도 시설 //////////////////////////////////////////////////
// 중수도시설 cellClick 이벤트 (상세정보)
const rswtCellClick = (index: number | undefined, headId: string) => {
if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) {
// setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] });
// setViewMode('detail')
onFeatureSelect(rswtrTable[index]);
onClose();
}
}
useEffect(() => {
if (!rswtSttn.isInit) {
rswtSttnInit(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, rswtSttn.isInit, rswtSttnInit]);
useEffect(() => {
const data = tableData(rswtSttnRes, { code });
const chData = data
.map((item: customRswtBdst) => {
return {
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0]?.rswtrObj,
chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N',
instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst,
instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt,
archArea: item?.archArea.split('.')[0] !== '-1' ? item?.archArea.split('.')[0] : '확인불가'
}
})
.filter(
(item: customRswtBdst) =>
item.reuseBuilding[0]?.rswtrObj !== undefined
);
setRswtrTable(chData);
}, [code, rswtSttnRes]);
// FIXME: 윤선화 차장님 요구사항으로 엑셀 다운로드 삭제.
// const rswtSttnExcelDownload = useCallback(() => {
// if (rswtSttn.excelList.length > 0) {
// if (rswtSttn.excelList.length === 0) return;
// excelDownload(
// rswtSttn.excelList,
// "중수도시설 현황",
// "sample_rswtSttn.xlsx",
// "A1:W3",
// );
// }
// }, [rswtSttn.excelList])
useEffect(() => {
rswtSttnSearch(false, rswtrPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, rswtSttnSearch, rswtrPage]);
///////////////////////////////////////////// 중수도 시설 //////////////////////////////////////////////////
///////////////////////////////////////////// 온배수 시설 //////////////////////////////////////////////////
// 온배수시설 cellClick 이벤트 (상세정보)
const htdflCellClick = (index: number | undefined) => {
if (index !== undefined && htdflTable !== undefined) {
onFeatureSelect(htdflTable[index]);
onClose();
}
};
useEffect(() => {
if (!htdflSttn.isInit) {
initHtdfl(false, 1, "20", "0", '', code.cityCd, code.sggCd);
}
}, [code.cityCd, code.sggCd, htdflSttn.isInit, initHtdfl]);
useEffect(() => {
const data = tableData(htdflSttnRes, { code });
const chData = data
.map((item: any) => ({
...item,
...item.reuseBuilding[0],
...item.reuseBuilding[0].htdflObj,
chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N",
}))
.filter((item: any) => item.htdflObj !== undefined);
setHtdflTable(chData);
}, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]);
useEffect(() => {
htdflSttnSearch(true, htdflPage, "20", '', '', code.cityCd, code.sggCd);
}, [code.cityCd, code.sggCd, htdflPage, htdflSttnSearch]);
///////////////////////////////////////////// 온배수 시설 //////////////////////////////////////////////////
///////////////////////////////////////////// 하수처리 시설 //////////////////////////////////////////////////
useEffect(() => {
if (code.sggCd.length && pbnstHasuRes.length > 0) {
setHasuTable(tableData(pbnstHasuRes, { code }));
}
}, [code, pbnstHasuRes]);
useEffect(() => {
if (!pbnst.isInit) {
pubHasuInit("1", "0");
}
}, [pbnst.isInit, pubHasuInit]);
useEffect(() => {
pubHasuInit(String(hasuPage), "0");
}, [hasuPage, pubHasuInit]);
const hasuCellClick = (index: number | undefined) => {
if (index !== undefined && pbnstHasuRes !== undefined) {
onFeatureSelect(pbnstHasuRes[index]);
onClose();
}
};
///////////////////////////////////////////// 하수처리 시설 //////////////////////////////////////////////////
///////////////////////////////////////////// 수요처 관리 //////////////////////////////////////////////////
useEffect(() => {
if (!coSttn.isInit) {
coSttnInit("1", "13", "0");
}
}, [coSttn.isInit, coSttnInit]);
const coMngCellClick = (index: number | undefined) => {
if (index !== undefined && mngTable !== undefined) {
onFeatureSelect(mngTable[index]);
onClose();
}
};
useEffect(() => {
if (coSttnRes) {
if (coSttnRes?.length > 0 && code.cityCd.length > 0) {
const data = tableData(coSttnRes, { code });
const chData = data.map((item: any) => ({
...item,
telno: formatPhoneNumber(item.telno || undefined),
regDt: moment(item.regDt).format("YYYY-MM-DD"),
dmdInfoId: `${item.dmdInfoId.slice(0, 3)}-${item.dmdInfoId.slice(3, 5)}-${item.dmdInfoId.slice(5, 10)}-${item.dmdInfoId.slice(10)}`
}));
// .filter((item: any) => item !== undefined);
setMngTable(chData);
}
}
}, [coSttnRes, code]);
useEffect(() => {
coSttnSearch(String(mngPage), "13", String(0));
}, [coSttnSearch, mngPage]);
///////////////////////////////////////////// 수요처 관리 //////////////////////////////////////////////////
// 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수
const renderConditionalContent = () => {
const actTabData = tabData.find((tab) => tab.id === activeTab);
let layerId: string | undefined | null = getSelectedLayers();
if (layerId === null || layerId === undefined) {
layerId = seletectedLayerNm || '';
}
//하수처리시설
const isPrcsFclt = layerId == 'tb_il_swage_prcs_fclt'
// 재이용시설
const isPbnst = layerId == 'tb_wr_bdst_ruse_fclt';
// 빗물이용시설
const isRain = layerId == 'tb_wr_rnwtr_utztn_fclt';
// 중수도시설
const isRswt = layerId == 'tb_wr_rswtr_fclt';
// 온배수시설
const isHtdfl = layerId == 'tb_wr_htdfl_fclt';
// 수요처관리
const isReuseMng = layerId == 'tb_wr_ruse_fclt_dmd_co';
if (isPrcsFclt) {
if (viewMode == 'list') {
return (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center mb-4">
<Title title="하수처리시설 현황" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={hasuTable || []}
headlist={pubFacHeadList}
pageSize={10}
total={5000}
headName={""}
cellClick={hasuCellClick}
onPageChange={setHasuPage}
/>
</div>
)
} else {
return (
<div style={{ overflowX: 'hidden' }}>
<div className="flex justify-between items-center mb-4">
<Title title="하수처리시설 상세정보" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={isRuseReg?.data || []}
headlist={pubFacHeadList.slice(0, 4)}
pageSize={1}
total={0}
headName={"위치정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
<UiTable
tableData={isRuseReg?.data || []}
headlist={pubFacHeadList.slice(4)}
pageSize={1}
total={0}
headName={"기본정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
</div>
)
}
}
if (isPbnst) {
if (viewMode == 'detail') {
// 재이용시설 상세
return (
<>
<div className="flex justify-between items-center mb-4">
<Title title="하수재이용시설 상세정보" />
</div>
<div className="flex justify-between items-center mb-8">
<UITab
defaultValue={activeTab}
tabList={tabData.map((tab) => ({
value: tab.id,
label: tab.name,
onClick: () => setActiveTab(tab.id),
}))}
/>
</div>
<PbnstSttnModal
data={actTabData?.data[0] || ({} as customPbnst)}
titleVisible={false}
/>
</>
);
} else {
// 재이용시설 목록
return (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="하수재이용시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={bdstTable || []}
headlist={pbnstSttn.pbnstSttHeadList}
pageSize={20}
total={pbnstSttnResCount || 0}
cellClick={(index: number | undefined) => {
pbnstCellClick(index);
}}
onPageChange={onPageChangeHandler}
>
<HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} />
</UiTable>
</div>
);
}
}
if (isRain) {
if (viewMode == 'detail') {
// 빗물이용시설 상세
return (
<SttnDetailModal type={"RNB"} formObj={rainDetailModal.data as customRainBdst} />
);
} else {
// 빗물이용시설 목록
return (
<>
<div className="flex justify-between items-center">
<Title title="빗물이용시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={rainTable || []}
headlist={rainSttn.rainSttHeadList}
pageSize={20}
total={rainSttnResCount}
cellClick={(index: number | undefined, headId: string) => {
rainCellClick(index ?? undefined, headId);
}}
onPageChange={setRainPage}
>
<HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} />
</UiTable>
</>
);
}
}
if (isRswt) {
if (viewMode == 'detail') {
// 중수도시설 상세
return (
<SttnDetailModal type={"RST"} formObj={rswtrDetailModal.data as customRswtBdst} />
);
} else {
// 중수도시설 목록
return (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="중수도시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={rswtSttnExcelDownload}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={rswtrTable || []}
headlist={rswtSttn.rswtSttHeadList}
pageSize={20}
total={rswtSttnResCount}
cellClick={(index: number | undefined, headId: string) => {
rswtCellClick(index, headId);
}}
onPageChange={setRswtrPage}
>
<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />
</UiTable>
</div>
);
}
}
if (isHtdfl) {
if (viewMode == 'detail') {
// 온배수시설 상세
return (
<SttnDetailModal type={"HTB"} formObj={htdflDetailModal.data as customHtdflBdst} />
);
} else {
// 온배수시설 목록
return (
<div style={{ overflowY: 'auto' }}>
<div className="flex justify-between items-center">
<Title title="온배수시설 현황" />
{/* <div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>
엑셀다운로드
</Button>
</div> */}
</div>
<UiTable
headName={[]}
tableData={htdflTable || []}
headlist={htdflSttn.htdflSttHeadList}
pageSize={20}
total={htdflSttnResCount || 0}
cellClick={(index: number | undefined) => {
htdflCellClick(index ?? undefined);
}}
onPageChange={setHtdflPage}
>
<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />
</UiTable>
</div>
);
}
}
if (isReuseMng) {
if (viewMode == 'detail') {
return (
<div style={{ overflowX: 'hidden' }}>
<div className="flex justify-between items-center mb-4">
<Title title="수요처 상세정보" />
</div>
{/* 높이 조정 */}
<UiTable
tableData={isDmdModal?.data || []}
headlist={coSttHeadList.slice(0, 3)}
pageSize={1}
total={0}
headName={"위치정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
<UiTable
tableData={isDmdModal?.data || []}
headlist={coSttHeadList.slice(3)}
pageSize={1}
total={0}
headName={"기본정보"}
cellClick={() => { }}
onPageChange={() => { }}
/>
</div>
)
} else if(viewMode == 'list') {
return ( <div>
<div className="flex justify-between items-center">
<Title title="수요처 현황" />
</div>
<UiTable
headName={[]}
tableData={mngTable || []}
headlist={coSttHeadList}
pageSize={20}
total={coSttnResCount + 10 || 0}
cellClick={(index: number | undefined) => {
coMngCellClick(index);
// setUpdateData(coSttnRes[index as number]);
}}
onPageChange={(page) => {
setMngPage(page);
}}
></UiTable></div>)
}
}
return null; // 조건에 맞는 콘텐츠가 없을 경우
};
return (
viewMode == 'nothing' ? <></> :
<NewWindowPortal
onClose={memoizedOnClose}
title="시설 상세 정보"
width={viewMode == 'list' ? window.screen.availWidth : 1100}
height={viewMode == 'list' ? window.screen.availHeight : layerId == '하수처리시설' && viewMode == 'detail' ? 450 : 900}
scrollbars='yes'
resizable='no'>
{/* 체크된 레이어에 따른 조건부 렌더링 */}
{renderConditionalContent()}
</NewWindowPortal>
);
};
export default NewWindowPopup;

Some files were not shown because too many files have changed in this diff Show More