diff --git a/src/components/ShareGroup/ShareGroupBottomBar/ShareGroupBottomBar.tsx b/src/components/ShareGroup/ShareGroupBottomBar/ShareGroupBottomBar.tsx index 4f2eea4..a1f214b 100644 --- a/src/components/ShareGroup/ShareGroupBottomBar/ShareGroupBottomBar.tsx +++ b/src/components/ShareGroup/ShareGroupBottomBar/ShareGroupBottomBar.tsx @@ -21,7 +21,7 @@ const ShareGroupBottomBar: React.FC = ({ // 선택한 이미지들의 URL을 다운로드함 const imageUrls: string[] = srcs; const handleDownload = async (): Promise => { - await imageZipDownloader({ imageUrls }); + await imageZipDownloader(imageUrls); }; return ( diff --git a/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx b/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx index 8872974..3410d59 100644 --- a/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx +++ b/src/components/ShareGroup/ShareGroupCloudButton/ShareGroupCloudButton.tsx @@ -93,9 +93,7 @@ const ShareGroupCloudButton: React.FC = () => { const response = await getDownloadPhotosAll(shareGroupId, profileId); console.log(response.data.data); if (response.status === 200) { - await imageZipDownloader({ - imageUrls: response.data.data.photoDownloadUrlList, - }); + await imageZipDownloader(response.data.data.photoDownloadUrlList); } } catch (error) { console.log(error); diff --git a/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx b/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx index 154eb0a..63be298 100644 --- a/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx +++ b/src/components/ShareGroup/ShareGroupImageList/ShareGroupImageList.tsx @@ -11,6 +11,8 @@ import { useRecoilState } from 'recoil'; import ShareGroupBottomBar from '../ShareGroupBottomBar/ShareGroupBottomBar'; import { deletePhoto } from 'apis/deletePhoto'; import { useLocation, useNavigate } from 'react-router-dom'; +import { getPhotosAll, getPhotosEtc } from 'apis/getPhotosAll'; +import { getPhotos } from 'apis/getPhotos'; export interface itemProp { createdAt: string; @@ -24,14 +26,14 @@ export interface itemProp { const ShareGroupImageList = ({ items, maxPage, - getApi, + profileId, shareGroupId, loading, setLoading, }: { items: itemProp[]; maxPage: number; - getApi: (page: number) => Promise; + profileId: number; shareGroupId: number; // Add shareGroupId as a prop // infinite scroll loading을 위한 state loading: boolean; @@ -76,14 +78,12 @@ const ShareGroupImageList = ({ setIsModal(false); }; - // infinite scroll의 다음 아이템을 가져올지 결정하는 함수 - // 사진 삭제 const handleDelete = async () => { let photoToDelete: number[] = []; if (selectedImage) { const id = localItems.find( - (item) => item.w400PhotoUrl === selectedImage, + (item) => item.rawPhotoUrl === selectedImage, )?.photoId; if (id) photoToDelete.push(id); } else if (checkedImg) photoToDelete = checkedImg; @@ -109,8 +109,56 @@ const ShareGroupImageList = ({ if (!isChecked) setCheckedImg([]); }, [isChecked]); + // 사진 불러오기 API 호출 함수 + const handleApi = async ( + page: number, + profileId: number, + shareGroupId: number, + ): Promise => { + // page가 있으면 page를 넣어줌 + const reqDataWithPage = { + shareGroupId: shareGroupId, + profileId: profileId, + size: 20, + page: page, + }; + setLoading(true); + + try { + if (profileId === 0) { + const { status, data } = await getPhotosAll(reqDataWithPage); + if (status === 200) { + setLoading(false); + return data.photoInfoList; + } + } else if (profileId === -1) { + const { status, data } = await getPhotosEtc(reqDataWithPage); + if (status === 200) { + setLoading(false); + return data.photoInfoList; + } + } else { + const { status, data } = await getPhotos(reqDataWithPage); + if (status === 200) { + setLoading(false); + return data.photoInfoList; + } + } + return []; + } catch (e) { + alert('앨범 조회 중 오류가 발생하였습니다'); + console.error('Failed to fetch more items:', e); + return []; + } + }; + // 스크롤 이벤트 핸들러 const handleScroll = useCallback(() => { + console.log('scroll'); + if (page >= maxPage) { + setHasMore(false); // 페이지가 최대치에 도달하면 더 이상 로드하지 않도록 설정 + return; + } if (containerRef.current && !loading && hasMore) { const { scrollTop, scrollHeight, clientHeight } = containerRef.current; // 스크롤이 하단에 도달했는지 확인 @@ -123,10 +171,16 @@ const ShareGroupImageList = ({ // 페이지가 변경될 때마다 API 호출 useEffect(() => { - if (page > 0) { + if (page > 0 && hasMore) { + // hasMore가 true일 때만 API 호출 const fetchMoreItems = async () => { try { - await getApi(page); + const newItems: itemProp[] = await handleApi( + page, + profileId, + shareGroupId, + ); + setLocalItems((prevItems) => [...prevItems, ...newItems]); // 새로운 아이템을 기존 아이템에 추가 } catch (error) { console.error('Failed to fetch more items:', error); } finally { @@ -135,7 +189,7 @@ const ShareGroupImageList = ({ }; fetchMoreItems(); } - }, [page, getApi, setLoading]); + }, [page, handleApi, setLoading, hasMore]); // 스크롤 이벤트 리스너 등록 useEffect(() => { @@ -150,11 +204,6 @@ const ShareGroupImageList = ({ }; }, [handleScroll]); - useEffect(() => { - if (choiceMode) setIsChecked(true); - if (!isChecked) setCheckedImg([]); - }, [isChecked]); - return ( <> @@ -166,7 +215,7 @@ const ShareGroupImageList = ({ selected={false} isDownload={item.isDownload} onClick={() => - handleImageClick(i, item.photoId, item.w200PhotoUrl) + handleImageClick(i, item.photoId, item.rawPhotoUrl) } checked={checkedImg.includes(item.photoId)} /> diff --git a/src/pages/ShareGroup/ShareGroupDetailPage/DropDown.tsx b/src/pages/ShareGroup/ShareGroupDetailPage/DropDown.tsx index eaa4d80..1f1aa93 100644 --- a/src/pages/ShareGroup/ShareGroupDetailPage/DropDown.tsx +++ b/src/pages/ShareGroup/ShareGroupDetailPage/DropDown.tsx @@ -29,6 +29,7 @@ const DropDown: React.FC = ({ groupId }) => { setIsClicked(!isClicked); }; + const handleItemClick = (idx: number, profileId: number, name: string) => { if (name === '모든 사진') { setPhotoType('all'); diff --git a/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx b/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx index 130dde0..ab99a3f 100644 --- a/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx +++ b/src/pages/ShareGroup/ShareGroupDetailPage/ShareGroupDetailPage.tsx @@ -31,7 +31,17 @@ const ShareGroupDetailPage: React.FC = () => { const handleApi = async (page: number): Promise => { // page가 있으면 page를 넣어줌 - const reqDataWithPage = { ...requestData, page: page }; + const reqDataWithPage = profileId + ? { + shareGroupId: location.state.shareGroupId, + profileId: profileId, + size: 20, + page: page, + } + : { + ...requestData, + page: page, + }; try { if (requestType === 'all') { const { status, data } = await getPhotosAll(reqDataWithPage); @@ -99,8 +109,8 @@ const ShareGroupDetailPage: React.FC = () => { diff --git a/src/utils/ImageZipDownloader.ts b/src/utils/ImageZipDownloader.ts index c3b14d4..f95eb97 100644 --- a/src/utils/ImageZipDownloader.ts +++ b/src/utils/ImageZipDownloader.ts @@ -1,86 +1,20 @@ -import JSZip from 'jszip'; - -// 모바일 장치인지 확인 -const isMobile = (): boolean => { - if (window.matchMedia('(max-width: 767px)').matches) { - return true; - } - // 두 번째 방법 - const userAgent = navigator.userAgent; - - // iOS, Android, Windows Phone 등의 모바일 장치를 감지 - if (/android/i.test(userAgent)) { - return true; - } - - if (/iPhone|iPad|iPod/i.test(userAgent)) { - return true; - } - - if (/windows phone/i.test(userAgent)) { - return true; - } - - // 기타 모바일 장치 감지 - if (/mobile/i.test(userAgent)) { - return true; - } - - return false; -}; - -// 이미지들을 jpeg로 변환하여 zip 파일로 다운로드 -const imageZipDownloader = async ({ - imageUrls, -}: { - imageUrls: string[]; -}): Promise => { - if (isMobile()) { - // 모바일일 경우 개별 다운로드 - for (const url of imageUrls) { - try { - const response = await fetch(url); - const blob = await response.blob(); - const fileName = url.split('/').pop() || 'image'; - const downloadUrl = window.URL.createObjectURL(blob); +// 이미지들을 jpeg로 변환하여 다운로드 +const imageZipDownloader = async (imageUrls: string[]) => { + imageUrls.forEach((url, index) => { + fetch(url) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); - a.href = downloadUrl; - a.download = fileName; + a.href = url; + a.download = `image-${index + 1}.jpeg`; // 파일 이름 지정 document.body.appendChild(a); a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(downloadUrl); - } catch (error) { - console.error('Error processing image:', error); - throw error; - } - } - } else { - // 모바일이 아니면 ZIP 파일로 압축하여 다운로드 - const zip = new JSZip(); - - for (const url of imageUrls) { - try { - const response = await fetch(url); - const blob = await response.blob(); - const fileName = url.split('/').pop() || 'image'; - zip.file(fileName, blob); - } catch (error) { - console.error('Error processing image:', error); - throw error; - } - } - - const zipBlob = await zip.generateAsync({ type: 'blob' }); - const zipUrl = window.URL.createObjectURL(zipBlob); - const a = document.createElement('a'); - a.href = zipUrl; - a.download = 'images.zip'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(zipUrl); - } + a.remove(); + window.URL.revokeObjectURL(url); + }) + .catch((error) => console.error('Image download failed:', error)); + }); }; export default imageZipDownloader;