Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 인기여행지 스켈레톤 적용 #70

Merged
merged 4 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions src/components/Tours/ToursCategory.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { RegionTypes, ToursCategoryProps } from '@/@types/tours.types';
import ToursCategoryItem from './ToursCategoryItem';
import { getPopularRegion } from '@api/region';
import { useQuery } from '@tanstack/react-query';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import ToursCategoryItemSkeleton from './ToursCategoryItemSkeleton';
import { v4 as uuidv4 } from 'uuid';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';

const ToursCategory = ({
selectedRegion,
setSelectedRegion,
}: ToursCategoryProps) => {
const regionsQuery = useQuery({
const { data, isLoading, error } = useQuery({
queryKey: ['regions'],
queryFn: () => getPopularRegion(),
});

if (regionsQuery.error) {
const [showSkeleton, setShowSkeleton] = useState(isLoading);

useEffect(() => {
if (isLoading) {
setShowSkeleton(true);
} else {
const timer = setTimeout(() => setShowSkeleton(false), 200);
return () => clearTimeout(timer);
}
}, [isLoading]);

if (error) {
console.log('error - 예외 처리');
}

Expand All @@ -28,18 +42,32 @@ const ToursCategory = ({
};

// '전체' 항목 추가
const regionsData = regionsQuery.data?.data.data.regions ?? [];
const regionsData = data?.data.data.regions ?? [];
const regions = [
{ name: '전체', areaCode: 0, subAreaCode: 0 },
{ name: '전체', areaCode: uuidv4(), subAreaCode: 0 },
...regionsData,
];

if (showSkeleton) {
return (
<div className="no-scrollbar my-3 flex w-[100%] overflow-scroll overflow-y-hidden bg-white">
<Swiper spaceBetween={8} slidesPerView={'auto'}>
{Array.from({ length: 10 }, (_, index) => (
<SwiperSlide key={index} className="w-[58px]">
<ToursCategoryItemSkeleton />
</SwiperSlide>
))}
</Swiper>
</div>
);
}

return (
<div className="no-scrollbar my-3 flex w-[100%] overflow-scroll overflow-y-hidden bg-white">
<Swiper spaceBetween={8} slidesPerView={'auto'}>
{regions.map((region: RegionTypes, index: number) => {
{regions.map((region: RegionTypes) => {
return (
<SwiperSlide key={index} className="w-[58px]">
<SwiperSlide key={uuidv4()} className="w-[58px]">
<ToursCategoryItem
name={region.name}
isSelected={region.name === selectedRegion}
Expand Down
9 changes: 9 additions & 0 deletions src/components/Tours/ToursCategoryItemSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const ToursCategoryItemSkeleton = () => {
return (
<div className="flex animate-pulse items-center justify-center">
<div className="h-[40px] w-[58px] rounded-[30px] bg-gray-300 px-[16px] py-[7px]"></div>
</div>
);
};

export default ToursCategoryItemSkeleton;
16 changes: 16 additions & 0 deletions src/components/Tours/ToursItemSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const ToursItemSkeleton = () => {
return (
<div className="flex-col">
<div className="relative">
<div className="rounded-1 h-[134px] max-h-[134px] w-[178px] animate-pulse rounded-[16px] bg-gray-300"></div>
</div>
<p className="headline1 mt-2 h-6 animate-pulse rounded bg-gray-300"></p>
<div className="caption1 ml-[2px] mt-2 flex space-x-2">
<div className="h-4 w-1/3 animate-pulse rounded bg-gray-300"></div>
<div className="h-4 w-1/4 animate-pulse rounded bg-gray-300"></div>
</div>
</div>
);
};

export default ToursItemSkeleton;
75 changes: 49 additions & 26 deletions src/components/Tours/ToursList.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { TourType, ToursListProps } from '@/@types/tours.types';
import ToursItem from './ToursItem';
import { getTours } from '@api/tours';
import { useInfiniteQuery } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
import ToursItem from './ToursItem';
import ToursItemSkeleton from './ToursItemSkeleton';

const ToursList = ({ selectedRegion }: ToursListProps) => {
const { fetchNextPage, hasNextPage, data, error } = useInfiniteQuery({
queryKey: ['tours', selectedRegion],
queryFn: ({ pageParam = 0 }) => getTours(selectedRegion, pageParam, 10),
initialPageParam: 0,
getNextPageParam: (lastPage) => {
const currentPage = lastPage?.data.data.pageable.pageNumber;
const totalPages = lastPage?.data.data.totalPages;

if (currentPage < totalPages - 1) {
return currentPage + 1;
}

return undefined;
},
});
const { fetchNextPage, hasNextPage, data, isLoading, error } =
useInfiniteQuery({
queryKey: ['tours', selectedRegion],
queryFn: ({ pageParam = 0 }) => getTours(selectedRegion, pageParam, 10),
initialPageParam: 0,
getNextPageParam: (lastPage) => {
const currentPage = lastPage?.data.data.pageable.pageNumber;
const totalPages = lastPage?.data.data.totalPages;

if (currentPage < totalPages - 1) {
return currentPage + 1;
}

return undefined;
},
});

const [showSkeleton, setShowSkeleton] = useState(isLoading);

useEffect(() => {
if (isLoading) {
setShowSkeleton(true);
} else {
const timer = setTimeout(() => setShowSkeleton(false), 200);
return () => clearTimeout(timer);
}
}, [isLoading]);

if (error) {
return <div>데이터를 불러오는 중 오류가 발생했습니다.</div>;
Expand All @@ -32,18 +46,27 @@ const ToursList = ({ selectedRegion }: ToursListProps) => {
loadMore={() => fetchNextPage()}
hasMore={hasNextPage}
loader={
<div className="loader" key={0}>
Loading ...
<div key={uuidv4()} className="flex justify-center">
<div
className="z-[105] mx-auto mt-10 h-8 w-8 animate-spin rounded-full border-[3px] border-solid border-current border-t-transparent text-[blue-600] dark:text-[#28d8ff]"
role="status"
aria-label="loading">
<div className="sr-only">Loading...</div>
</div>
</div>
}>
<div className="no-scrollbar grid grid-cols-2 gap-[15px] overflow-y-scroll">
{data?.pages.map((group, index) => (
<React.Fragment key={index}>
{group?.data.data.content.map((tour: TourType) => (
<ToursItem key={tour.id} tour={tour} />
<div className="no-scrollbar grid min-h-[500px] grid-cols-2 gap-[15px] overflow-y-scroll">
{showSkeleton
? Array.from({ length: 10 }, (_, index) => (
<ToursItemSkeleton key={index} />
))
: data?.pages.map((group) => (
<React.Fragment key={uuidv4()}>
{group?.data.data.content.map((tour: TourType) => (
<ToursItem key={uuidv4()} tour={tour} />
))}
</React.Fragment>
))}
</React.Fragment>
))}
</div>
</InfiniteScroll>
);
Expand Down
8 changes: 4 additions & 4 deletions src/components/common/nav/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@ const Nav = () => {
<div className="flex justify-center">
<HomeIcon fill={isActive('/') ? 'currentColor' : 'none'} />
</div>
<p className="caption2 mt-[3px] text-center">홈</p>
<p className="caption1 mt-[3px] text-center">홈</p>
</div>
<div
onClick={() => navigate('/')}
className="cursor-pointer flex-col items-center justify-center px-2">
<CalendarIcon />
<p className="caption2 mt-[3px] text-center text-xs/[11px]">일정</p>
<p className="caption1 mt-[3px] text-center text-xs/[11px]">일정</p>
</div>
<div
onClick={() => navigate('/')}
className="cursor-pointer flex-col items-center justify-center px-2">
<div className="flex justify-center">
<HeartIcon />
</div>
<p className="caption2 mt-[3px] text-center text-xs/[11px]">찜</p>
<p className="caption1 mt-[3px] text-center text-xs/[11px]">찜</p>
</div>
<div
onClick={() => navigate('/signin')}
className="cursor-pointer flex-col items-center justify-center px-1">
<div className="flex justify-center">
<UserIcon />
</div>
<p className="caption2 mt-[3px] text-center">내정보</p>
<p className="caption1 mt-[3px] text-center">내정보</p>
</div>
</div>
</nav>
Expand Down