본문 바로가기
Next.js

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

by code2772 2024. 11. 4.

[ 목차 ]

    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(): 배열의 각 요소를 변환하여 새로운 배열 생성

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

    반응형