From 4c256d69a303f673ab05d93a8881dc2a2475c11f Mon Sep 17 00:00:00 2001 From: swcho Date: Wed, 6 Aug 2025 16:59:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20-=20toast.tsx=20=EC=B6=94=EA=B0=80=20-?= =?UTF-8?q?=20=EC=B9=A8=EC=9E=85=EC=88=98=20=EB=B6=84=EC=84=9D,=20RDII=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=B2=84=ED=8A=BC=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=8B=9C=20=EC=8B=9C=EA=B0=84=20=EC=86=8C=EC=9A=94=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20toast=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(with-base-layout)/ii/BaseLayout.tsx | 230 ++++++++++-------- apps/sn/app/components/ui/toast.tsx | 55 +++++ 2 files changed, 182 insertions(+), 103 deletions(-) create mode 100644 apps/sn/app/components/ui/toast.tsx diff --git a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx index 5d5443db2..fcea807df 100644 --- a/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx +++ b/apps/sn/app/(with-base-layout)/ii/BaseLayout.tsx @@ -2,8 +2,10 @@ 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 { useState, type ReactNode } from 'react' +import { useEffect, type ReactNode } from 'react' import { CtpvSggPrcsFcltSelectBox } from '@/components/ui/ctpvSggPrcsFcltSelectBox' +import { confirmToast } from '@/components/ui/toast' +import { toast, Toaster } from "react-hot-toast"; type TitleName = { mainTitle: string @@ -85,6 +87,14 @@ const BaseLayout = ({ onAnalysis, }: BaseLayout) => { + // 컴포넌트 언마운트 시 confirmToast 정리 + useEffect(() => { + return () => { + // 컴포넌트가 언마운트될 때 global-confirm-toast ID를 가진 Toast 제거 + toast.dismiss("global-confirm-toast"); + }; + }, []); + // 파일 다운로드 const isChckFile = () => { // Excel 다운로드 함수가 있으면 Excel 다운로드 실행 @@ -103,118 +113,132 @@ const BaseLayout = ({ } } + const handleConfirm = () => { + // 기존 confirmToast가 있다면 먼저 제거 + toast.dismiss("global-confirm-toast"); + + confirmToast( + "분석에는 2분 내외의 시간이 소요될 수 있습니다.\n분석을 진행하시겠습니까?", + onAnalysis ?? (() => { }), + "text-xl font-medium text-gray-800" + ); + }; + return ( -
- - {/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */} - {selectBox?.title && ( -
-

{selectBox.title}

-
- )} -
- {showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링 - <> - - { // 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="ml-8 w-[100px]" - startYear={1900} // 시작 연도 설정 - /> - ~ - { // 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-[100px] mr-8" - startYear={1900} // 시작 연도 설정 - /> - - )} - { - setCtpvCd(sidoId); - setSggCd(0); - }} - onSigunguChange={setSggCd} - setSggName={setSggName} - selectedSwagePrcsFclt={selectedSwagePrcsFclt} - onSwagePrcsFcltChange={onSwagePrcsFcltChange} - setIsLoading={setIsLoading} - /> - {selectBox?.searchChk && ( -
- -
- )} -
-
- -
-

{titleName.firstCardTitle}

-
- {graphBtn && ( - - )} - {/* 그래프 있을 시 */} - {graphBtn && ( - - )} - {/* 분석 버튼 */} - {analysisBtn && ( - - )} -
-
- - -
- {/* 폼 정보 입력 칸 */} - {children?.[0]} -
- - -
- {children?.[1] != null && ( + <> +
-
-

{titleName?.secondCardTitle}

-
+ {/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */} + {selectBox?.title && ( +
+

{selectBox.title}

+
+ )} +
+ {showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링 + <> + + { // 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="ml-8 w-[100px]" + startYear={1900} // 시작 연도 설정 + /> + ~ + { // 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-[100px] mr-8" + startYear={1900} // 시작 연도 설정 + /> + + )} + { + setCtpvCd(sidoId); + setSggCd(0); + }} + onSigunguChange={setSggCd} + setSggName={setSggName} + selectedSwagePrcsFclt={selectedSwagePrcsFclt} + onSwagePrcsFcltChange={onSwagePrcsFcltChange} + setIsLoading={setIsLoading} + /> + {selectBox?.searchChk && ( +
+ +
+ )} +
+ + +
+

{titleName.firstCardTitle}

+
{graphBtn && ( - + )} {/* 그래프 있을 시 */} {graphBtn && ( - + + )} + {/* 분석 버튼 */} + {analysisBtn && ( + )}
-
{children?.[1]}
+ + +
+ {/* 폼 정보 입력 칸 */} + {children?.[0]} +
+ +
- )} - + {children?.[1] != null && ( + +
+

{titleName?.secondCardTitle}

+
+ {graphBtn && ( + + )} + {/* 그래프 있을 시 */} + {graphBtn && ( + + )} +
+
+
{children?.[1]}
+
+ )} + + + ) } diff --git a/apps/sn/app/components/ui/toast.tsx b/apps/sn/app/components/ui/toast.tsx new file mode 100644 index 000000000..b1e021651 --- /dev/null +++ b/apps/sn/app/components/ui/toast.tsx @@ -0,0 +1,55 @@ +import { Button } from "@common/components/ui"; +import toast from "react-hot-toast"; + +export const confirmToast = (content: string, saveFunc: () => void, contentClass?:string) => { + toast( + (t) => ( + +
{content}
+
+ + +
+
+ ), + { + id: "global-confirm-toast", // 중복 토스트 방지 + duration: Infinity, // 자동 사라짐 방지 + } + ); +}; + +export const showConfirmToast = (message: string) => { + toast.custom( + (t) => ( +
+
+ {message} +
{" "} +
+ +
+
+ ), + { duration: Infinity } + ); // 수동 닫기 +}; \ No newline at end of file