Files
frontend/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx
T

346 lines
11 KiB
TypeScript

/**
* ➡️ 경로 : I/I 분석 모든 페이지
*
* 📄 Base Layout
* 분석 페이지 기본 레이아웃 컴포넌트
*
* - 검색 및 필터링 UI 제공 (시도/시군구/하수처리시설 선택)
* - 날짜 범위 선택 기능
* - 파일 다운로드 및 삭제 버튼 지원
* - 분석 실행 버튼 및 확인 토스트 처리
* - 양식 파일 다운로드 링크 제공
* - 폼 제출 및 이벤트 핸들링
*/
"use client";
import { DateMonthPicker, Form } from "@common/business_components/ui";
import { Button, Label } from "@common/components/ui";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import type { ReactNode } from "react";
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
type TitleName = {
mainTitle: string;
firstCardTitle: string;
secondCardTitle?: string;
};
type SelectBox = {
labelNm?: Label;
searchChk?: boolean;
title?: string;
onSearchClick?: () => Promise<void>;
onResetClick?: () => void | Promise<void>;
};
type Label = {
first: string;
second: string;
third: string;
fourth: string;
};
// API로부터 받아올 데이터 타입을 정의 (SelectBox에 사용될 형태)
interface ApiSelectOption {
text: string;
val: string;
}
type BaseLayout = {
children?: ReactNode[];
titleName: TitleName;
form: any;
onSubmitEvent: Function;
graphBtn?: boolean;
analysisBtn?: string;
resultViewBtn?: string;
firstDivBtn?: boolean;
selectBox?: SelectBox;
selectionComponent?: ReactNode;
showDatePicker?: boolean;
ctpvCd: string;
sggCd: string;
sggName?: string;
sigungu?: ApiSelectOption;
selectedSwagePrcsFclt?: string;
onSwagePrcsFcltChange?: (value: string) => void;
aplcnYmFrom?: string;
setAplcnYmFrom?: (aplcnYmFrom: string) => void;
aplcnYmTo?: string;
setAplcnYmTo?: (aplcnYmTo: string) => void;
setCtpvCd: (ctpv: string) => void;
setSggCd: (sgg: string) => void;
setSggName?: (sggName: string) => void;
setSwtrmtFcltNm?: (swtrmtFcltNm: string) => void;
defaultSwtrmtFcltNm?: string;
setIsLoading?: (isLoading: boolean) => void;
onExcelDownload?: () => void;
onDeleteFile?: () => void;
onAnalysis?: () => void | Promise<void>;
onResultView?: () => void;
selectedRowData?: any;
isIr?: boolean;
isRdii?: boolean;
};
const BaseLayout = ({
children,
titleName,
form,
onSubmitEvent,
graphBtn = false,
analysisBtn,
resultViewBtn,
selectBox,
selectionComponent,
showDatePicker = false,
ctpvCd,
sggCd,
sggName,
sigungu,
selectedSwagePrcsFclt,
onSwagePrcsFcltChange,
aplcnYmFrom,
setAplcnYmFrom,
aplcnYmTo,
setAplcnYmTo,
setCtpvCd,
setSggCd,
setSggName,
setSwtrmtFcltNm,
defaultSwtrmtFcltNm,
setIsLoading,
onExcelDownload,
onDeleteFile,
onAnalysis,
onResultView,
isIr = false,
isRdii = false,
}: BaseLayout) => {
// 분석 실행 전 CS와 동일한 전역 확인 대화상자를 호출한다.
const { confirm } = useConfirm();
// 파일 다운로드
const isChckFile = () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onExcelDownload) {
onExcelDownload();
return;
}
return true;
};
// 파일 삭제
const isDeleteFile = async () => {
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
if (onDeleteFile) {
onDeleteFile();
return;
}
};
const handleConfirm = async () => {
const shouldRunAnalysis = await confirm(
"분석에는 2분 내외의 시간이 소요될 수 있습니다.\n분석을 진행하시겠습니까?",
);
if (!shouldRunAnalysis) return;
await onAnalysis?.();
};
return (
<>
<Form {...form} className="flex min-h-screen flex-col">
<section className="card card-border">
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
{selectBox?.title && (
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{selectBox.title}
</h3>
</div>
)}
<div className="flex w-full flex-wrap items-center gap-3 p-[1.2rem]">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="whitespace-nowrap text-label">
상수사용기간
</Label>
<DateMonthPicker
field={{
value:
aplcnYmFrom ||
(() => {
// aplcnYm이 없으면 현재 연월을 기본값으로 설정
const now = new Date();
const year = now.getFullYear();
// const month = String(now.getMonth() + 1).padStart(2, '0');
// return `${year}${month}`;
return `${year}01`;
})(), // yyyyMM 형식
onChange: (value: string) => {
setAplcnYmFrom?.(value); // string 값을 직접 전달
},
}}
selectClass="w-[10rem]"
startYear={1900} // 시작 연도 설정
/>
<span className="text-muted-foreground">~</span>
<DateMonthPicker
field={{
value:
aplcnYmTo ||
(() => {
// aplcnYm이 없으면 현재 연월을 기본값으로 설정
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(
2,
"0",
);
return `${year}${month}`;
})(), // yyyyMM 형식
onChange: (value: string) => {
setAplcnYmTo?.(value); // string 값을 직접 전달
},
}}
selectClass="w-[10rem]"
startYear={1900} // 시작 연도 설정
/>
</>
)}
{/* 침입수 분석 실행처럼 선택 구조가 다른 화면은 커스텀 selector를 주입하고, 나머지는 기존 시설 selector를 유지한다. */}
{selectionComponent ?? (
<CtpvSggPrcsFcltSelectBox
sido={ctpvCd}
sigungu={sggCd}
sggName={sggName}
onSidoChange={(sidoId: string) => {
setCtpvCd(sidoId);
setSggCd("0");
}}
onSigunguChange={setSggCd}
setSggName={setSggName}
selectedSwagePrcsFclt={selectedSwagePrcsFclt}
onSwagePrcsFcltChange={onSwagePrcsFcltChange}
setSwtrmtFcltNm={setSwtrmtFcltNm}
defaultSwtrmtFcltNm={defaultSwtrmtFcltNm}
setIsLoading={setIsLoading}
/>
)}
{selectBox?.searchChk && (
<div className="ml-auto flex items-center gap-2">
<Button
type="button"
onClick={() => selectBox.onSearchClick?.()}
>
조회
</Button>
{selectBox.onResetClick ? (
<Button
type="button"
color="white"
onClick={() => selectBox.onResetClick?.()}
>
초기화
</Button>
) : null}
</div>
)}
</div>
</section>
<section className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{titleName.firstCardTitle}
</h3>
<div className="flex flex-wrap items-center gap-2">
{graphBtn && (
<Button type="button" onClick={isChckFile}>
엑셀다운로드
</Button>
)}
{/* 침입수 분석 */}
{isIr && (
<Button type="button">
<a
href="/samples/ir_setting_sample.zip"
download="침입수 분석 업로드 파일 양식.zip"
>
양식다운로드
</a>
</Button>
)}
{/* RDII 분석 */}
{isRdii && (
<Button type="button">
<a
href="/samples/rdii_sample.zip"
download="RDII 분석 업로드 파일 양식.zip"
>
양식다운로드
</a>
</Button>
)}
{/* 분석 버튼 */}
{analysisBtn && (
<Button type="button" onClick={handleConfirm}>
{analysisBtn}
</Button>
)}
{resultViewBtn && (
<Button
type="button"
color="white"
onClick={onResultView}
>
{resultViewBtn}
</Button>
)}
{graphBtn && (
<Button
type="button"
color="red"
onClick={isDeleteFile}
>
삭제
</Button>
)}
</div>
</div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmitEvent)}
className="flex flex-grow flex-col"
>
<div className="min-w-0 flex-grow p-[1.2rem]">
{/* 폼 정보 입력 칸 */}
{children?.[0]}
</div>
</form>
</Form>
</section>
{children?.[1] != null && (
<section className="card card-border">
<div className="card-header flex items-center">
<h3 className="text-2xl font-medium text-label">
{titleName?.secondCardTitle}
</h3>
<div className="ml-auto flex items-center gap-2">
{graphBtn && (
<Button type="button">
엑셀다운로드
</Button>
)}
{/* 그래프 있을 시 */}
{graphBtn && (
<Button type="button" color="red">
삭제
</Button>
)}
</div>
</div>
<div className="p-[1.2rem]">{children?.[1]}</div>
</section>
)}
</Form>
</>
);
};
export default BaseLayout;