Next.js

[Next.js] TOAST UI POPUP(팝업 창) 활용 2편

code2772 2024. 11. 4. 08:36
728x90
반응형

들어가며

이번에는 Toast UI 를 활용해서 간단하게 메인 페이지에 팝업창을 띄우는 예시를 포스팅하게 되었다.

 

Toast UI 기본 설명

https://hunseop2772.tistory.com/360

 

[Next.js] TOAST UI 이미지 Blob 처리 및 활용 1편

들어가며해당 코드는 회사 코드는 아니고 간단하게 요약한 코드여서 바로 사용한다고 해도 퀄리티에 문제가 될 수 있다. 해당 Toast UI를 사용하게 된 이유는 기존 회사 업무를 진행하며 WEB, API, GW

hunseop2772.tistory.com

 

 

 

POPUP 창 미리보기

공지사항/팝업/자료실 등 등록 페이지

 

제목은 헤더 쪽 문구를 표현하고 시작일과 종료일은 팝업이 뜨는 기간을 설정하게 하였다.

 

 

popup

 

해당 설정한 날짜와를 DB에 저장하게 되고 이 날짜와 현재 날짜가 동일하면 메인페이지에 여러 개가 뜨게 만들었다. 팝업창에서  '오늘 하루 보지 않기' 를 클릭하게 되면 하루 동안 보이지 않는 기능이 있다. 해당 코드 일부는 하단에서 간단하게 주석으로 설명해보겠다.

 

1. 팝업 상태 관리와 필터링 (LoginForm.tsx):

// Redux로부터 팝업 데이터를 가져옴
const { popups } = useSelector((state: RootState) => state.noti);
// 현재 표시할 팝업들을 관리하는 state
const [currentPopups, setCurrentPopups] = useState([]);

useEffect(() => {
// popups가 존재하고 길이가 0보다 클 때만 실행
    if (popups && popups.length > 0) {
// 현재 날짜를 YYYY-MM-DD 형식으로 가져옴
        const today = new Date().toISOString().split('T')[0];

// Array.filter() 메소드를 사용하여 표시할 팝업 필터링
        const validPopups = popups.filter(popup => {
// 팝업의 시작일과 종료일을 YYYY-MM-DD 형식으로 변환
            const startDate = new Date(popup.popupStartDate).toISOString().split('T')[0];
            const endDate = new Date(popup.popupEndDate).toISOString().split('T')[0];

// localStorage에서 "보지 않기" 설정 확인// `popup_${popup.id}_dontShowUntil` 형식의 키를 사용
            const dontShowUntil = localStorage.getItem(`popup_${popup.id}_dontShowUntil`);

// 날짜 유효성 검사 (시작일 <= 오늘 <= 종료일)
            const isValidDate = today >= startDate && today <= endDate;

// "보지 않기" 설정 확인// dontShowUntil이 없거나(null) 저장된 날짜가 오늘보다 이전인 경우 true
            const shouldShow = !dontShowUntil || dontShowUntil < today;

// 두 조건이 모두 true일 때만 팝업 표시
            return isValidDate && shouldShow;
        });

// 필터링된 팝업들을 state에 저장
        setCurrentPopups(validPopups);
    }
}, [popups]);// popups가 변경될 때마다 실행

 

2. 팝업 닫기 처리 (LoginForm.tsx):

// useCallback을 사용하여 메모이제이션된 함수 생성
const handleClosePopup = useCallback((popupId: number, dontShowToday: boolean) => {
    if (dontShowToday) {
// 오늘 날짜를 YYYY-MM-DD 형식으로 가져옴
        const today = new Date().toISOString().split('T')[0];
// localStorage에 저장 - 키: popup_[팝업ID]_dontShowUntil, 값: 오늘 날짜
        localStorage.setItem(`popup_${popupId}_dontShowUntil`, today);
    }

// 현재 팝업 목록에서 해당 팝업 제거// prev: 이전 상태값
    setCurrentPopups(prev => prev.filter(popup => popup.id !== popupId));
}, []);// 의존성 배열이 비어있으므로 컴포넌트가 마운트될 때 한 번만 생성

 

3. 팝업 컴포넌트 구현 (BoardDetailPop.tsx):

// 타입스크립트 인터페이스 정의
interface BoardDetailModalProps {
    isOpen: boolean;// 팝업 열림 상태
    onClose: (dontShowToday: boolean) => void;// 닫기 핸들러
    board: any;// 팝업 데이터
    initialPosition: { x: number; y: number };// 팝업 초기 위치
}

const BoardDetailPop: React.FC<BoardDetailModalProps> = ({
    isOpen,
    onClose,
    board,
    initialPosition
}) => {
// "오늘 하루 보지 않기" 체크박스 상태 관리
    const [dontShowToday, setDontShowToday] = useState<boolean>(false);

// 팝업 닫기 핸들러
    const handleClose = () => {
// 부모 컴포넌트로 체크박스 상태 전달
        onClose(dontShowToday);
    };

// board 데이터가 없으면 null 반환
    if (!board) return null;

    return (
        <Modal
            isOpen={isOpen}
            onClose={handleClose}
            title={board.title}
            width={650}
            height={900}
            initialPosition={initialPosition}
        >
            <Box sx={{ p: 2, height: '100%', overflow: 'auto' }}>
                {/* 팝업 내용 */}
                <Viewer initialValue={board.content} />

                {/* 팝업 타입이 POPUP일 때만 "오늘 하루 보지 않기" 표시 */}
                {board.type === 'POPUP' && (
                    <Box sx={{
                        mt: 2,
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center'
                    }}>
                        <FormControlLabel
                            control={
                                <Checkbox
                                    checked={dontShowToday}
                                    onChange={(e) => setDontShowToday(e.target.checked)}
                                />
                            }
                            label="오늘 하루 보지 않기"
                        />
                        <Button variant="contained" onClick={handleClose}>
                            닫기
                        </Button>
                    </Box>
                )}
            </Box>
        </Modal>
    );
};

 

4. 팝업 렌더링 (LoginForm.tsx):

return (
    <div id="login_wrap">
        {/* 현재 표시할 팝업들을 map으로 순회하여 렌더링 */}
        {currentPopups.map((popup, index) => (
            <BoardDetailPop
                key={popup.id}// React 리스트 렌더링시 필수 key prop
                isOpen={true}
                onClose={(dontShowToday) => handleClosePopup(popup.id, dontShowToday)}
                board={popup}
// 팝업창들이 겹치지 않도록 위치 설정
                initialPosition={{
                    x: 100 + (index * 50),
                    y: 20 + (index * 50)
                }}
            />
        ))}
        {/* 로그인 폼 렌더링 */}
    </div>
);

 

주요 문법 포인트:

  1. 타입스크립트 인터페이스:
    • BoardDetailModalProps로 props의 타입을 명확히 정의
    • React.FC<Props>로 함수형 컴포넌트 타입 지정
  2. useState 훅:
    • const [state, setState] = useState<Type>(initialValue)
    • 제네릭으로 상태의 타입을 명시
  3. useEffect 훅:
    • useEffect(() => { ... }, [dependencies])
    • 의존성 배열의 값이 변경될 때마다 실행
  4. useCallback 훅:
    • 함수를 메모이제이션하여 불필요한 리렌더링 방지
    • useCallback(() => { ... }, [dependencies])
  5. localStorage API:
    • setItem(key, value): 데이터 저장
    • getItem(key): 데이터 조회
    • 문자열 형태로만 데이터 저장 가능
  6. 조건부 렌더링:
    • && 연산자 사용: {condition && <Component />}
    • 삼항 연산자 사용: {condition ? <Component1 /> : <Component2 />}
  7. 배열 메서드:
    • filter(): 조건에 맞는 요소만 필터링
    • map(): 배열의 각 요소를 변환하여 새로운 배열 생성

이러한 방식으로 구현된 "오늘 하루 보지 않기" 기능은 사용자 경험을 향상시키면서도 효율적인 상태 관리와 데이터 지속성을 제공합니다.

반응형