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

refactor : 리스트 아이템 컴포넌트 수정&리팩토링 #462

Merged
4 changes: 4 additions & 0 deletions src/assets/Location.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/rightArrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions src/components/Common/LetterListItem/Category.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type BoxType = 'SEND' | 'RECEIVE';
type LetterType = 'LETTER' | 'REPLY_LETTER';

interface CategoryProps {
boxType: BoxType;
letterType: LetterType;
}

const categoryStyle = 'text-sm';

export const Category = ({ boxType, letterType }: CategoryProps) => {
const classification = `${boxType}-${letterType}`;
switch (classification) {
case 'SEND-LETTER':
return <div className={categoryStyle}>보낸 편지</div>;
case 'SEND-REPLY_LETTER':
return <div className={categoryStyle}>보낸 답장</div>;
case 'RECEIVE-LETTER':
return <div className={categoryStyle}>받은 편지</div>;
case 'RECEIVE-REPLY_LETTER':
return <div className={categoryStyle}>받은 답장</div>;
default:
}
};
13 changes: 2 additions & 11 deletions src/components/StoragePage/LetterDateGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import { LetterListItem } from './LetterListItem';
import { DeleteLetterType } from '@/types/letter';

interface Letter {
letterId: number;
title: string;
label: string;
letterType: string;
boxType: string;
createdAt: string;
}
import { DeleteLetterType, StorageLetterDataType } from '@/types/letter';

type LetterDateGroupProps = {
date: string;
letters: Letter[];
letters: StorageLetterDataType[];
checkedItems: DeleteLetterType[];
handleSingleCheck: (
checked: boolean,
Expand Down
144 changes: 99 additions & 45 deletions src/components/StoragePage/LetterListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Itembox } from '@/components/Common/Itembox/Itembox';
import { BottleLetter } from '@/components/Common/BottleLetter/BottleLetter';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { DeleteLetterType, storageLetterType } from '@/types/letter';
import { match } from 'ts-pattern';
import {
DeleteLetterType,
StorageKeywordLetter,
StorageLetterDataType,
StorageMapArchivedLetter,
StorageMapReceivedLetter,
StorageMapSentLetter
} from '@/types/letter';
import { Category } from '../Common/LetterListItem/Category';

interface Letter {
letterId: number;
title: string;
label: string;
letterType: string;
boxType: string;
createdAt: string;
}
export const ASSETS = {
LOCATION: '/src/assets/Location.svg',
RIGHT_ARROW: '/src/assets/rightArrow.svg'
} as const;

type LetterListItemProps = {
letter: Letter;
letter: StorageLetterDataType;
checked: boolean;
handleSingleCheck: (
checked: boolean,
Expand All @@ -28,39 +30,91 @@ export const LetterListItem = ({
handleSingleCheck
}: LetterListItemProps) => {
const navigate = useNavigate();

const { letterType } = useParams();
const { selectedLetterType } = useParams();
const [searchParams] = useSearchParams();
const filterType = searchParams.get('filtertype');

const renderCategory = (boxType: string, letterType: string) => {
const condition = `${boxType}-${letterType}`;
switch (condition) {
case 'SEND-LETTER':
return '보낸 편지';
case 'SEND-REPLY_LETTER':
return '보낸 답장';
case 'RECEIVE-LETTER':
return '받은 편지';
case 'RECEIVE-REPLY_LETTER':
return '받은 답장';
const getNavigatePath = (letter: StorageLetterDataType) => {
switch (selectedLetterType) {
case 'keyword':
return `/letter/keyword/${letter.letterType}/${filterType}/${letter.letterId}`;
case 'map':
return `/letter/map/${filterType}/${letter.letterId}`;
case 'bookmark':
return `/letter/map/${filterType}/bookmark/${letter.letterId}`;
default:
throw new Error(
`Unsupported letter type: ${selectedLetterType}`
);
}
};

const getNavigatePath = (letter: Letter) => {
return match<storageLetterType>(letterType as storageLetterType)
.with(
'keyword',
() =>
`/letter/keyword/${letter.letterType}/${filterType}/${letter.letterId}`
)
.with('map', () => `/letter/map/${filterType}/${letter.letterId}`)
.with(
'bookmark',
() => `/letter/map/${filterType}/bookmark/${letter.letterId}`
)
.exhaustive();
const renderListContent = () => {
switch (selectedLetterType) {
case 'keyword': {
const keywordLetter = letter as StorageKeywordLetter;
return (
<>
<Category
boxType={keywordLetter.boxType}
letterType={keywordLetter.letterType}
/>
<h3 className="text-md font-bold">
{keywordLetter.title}
</h3>
</>
);
}
case 'map': {
const mapLetter = letter as
| StorageMapSentLetter
| StorageMapReceivedLetter;

const getUserNicknameLabel = (
letter: StorageMapSentLetter | StorageMapReceivedLetter
) => {
if (letter.type === 'PUBLIC') return '';
// Target
const prefix =
'targetUserNickname' in letter ? 'To.' : 'From.';
const nickname =
'targetUserNickname' in letter
? letter.targetUserNickname
: letter.senderNickname;

return nickname ? `${prefix} ${nickname}` : '';
};
return (
<>
<div className="text-sm">
{getUserNicknameLabel(mapLetter)}
</div>
<div className="flex flex-row gap-1 items-center">
<h3 className="text-md font-bold">
{letter.title}
</h3>
</div>
<div className="flex flex-row gap-1">
<img src="/src/assets/Location.svg"></img>
<div className="text-sm">
서울특별시 동대문구 어디어디
</div>
</div>
</>
);
}
case 'bookmark': {
const bookmarkLetter = letter as StorageMapArchivedLetter;
return (
<>
<h3 className="text-md font-bold">
{bookmarkLetter.title}
</h3>
<div>샘플 위치가 들어갑니다.</div>
</>
);
}
}
};

return (
Expand All @@ -81,15 +135,15 @@ export const LetterListItem = ({
className="flex flex-row gap-4 w-full h-[90px] items-center p-4 rounded-lg bg-sample-gray cursor-pointer"
onClick={() => navigate(getNavigatePath(letter))}
>
<Itembox>
<BottleLetter Letter={letter} />
</Itembox>
<div className="flex flex-col h-full">
<div className="text-[12px] text-gray-500 mt-2">
{renderCategory(letter.boxType, letter.letterType)}
<div className="flex flex-row gap-4 w-full items-center">
<div className="w-[67px] h-[67px] p-2 bg-white rounded-full">
<BottleLetter Letter={letter} />
</div>
<div className="flex flex-col h-full">
{renderListContent()}
</div>
<h3 className="text-sm font-bold">{letter.title}</h3>
</div>
<img src={ASSETS.RIGHT_ARROW} />
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/StoragePage/StorageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ROWS_PER_PAGE = 10;

export const StorageList = () => {
const queryClient = useQueryClient();
const { letterType } = useParams();
const { selectedLetterType } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const filterType = searchParams.get('filtertype');
const { ref, inView } = useInView();
Expand All @@ -24,7 +24,7 @@ export const StorageList = () => {
const [checkedItems, setCheckedItems] = useState<DeleteLetterType[]>([]);

const getApiEndpoint = () => {
return match<storageLetterType>(letterType as storageLetterType)
return match<storageLetterType>(selectedLetterType as storageLetterType)
.with('keyword', () => `/letters/saved/${filterType}`)
.with('map', () => `/map/${filterType}`)
.with('bookmark', () => '/map/archived')
Expand Down
12 changes: 2 additions & 10 deletions src/hooks/useInfiniteStorageFetch.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { useMemo } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { getLetter } from '@/service/storage/getLetter';

interface Letter {
letterId: number;
title: string;
label: string;
letterType: string;
boxType: string;
createdAt: string;
}
import { StorageLetterDataType } from '@/types/letter';

type useInfiniteStorageFetchParams = {
apiEndpoint: string;
Expand Down Expand Up @@ -65,7 +57,7 @@ export const useInfiniteStorageFetch = ({
}
const allLetters = data.pages.flatMap((page) => page.content);
const grouped = allLetters.reduce(
(acc: { [key: string]: Letter[] }, letter) => {
(acc: { [key: string]: StorageLetterDataType[] }, letter) => {
const date = new Date(letter.createdAt)
.toISOString()
.split('T')[0];
Expand Down
76 changes: 40 additions & 36 deletions src/pages/Storage/StoragePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,44 @@ import React, { useEffect } from 'react';
import { StorageList } from '@/components/StoragePage/StorageList';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';

type LetterType = 'keyword' | 'map' | 'bookmark';
export const StoragePage = () => {
const navigate = useNavigate();
const { selectedLetterType } = useParams();
const [searchParams, setSearchParams] = useSearchParams();

const getTranslateX = (path: LetterType) => {
const pathIndex =
const storageMenuList = [
{
type: 'keyword',
label: '키워드 편지'
},
{
keyword: 0,
map: 1,
bookmark: 2
}[path] || 0;
type: 'map',
label: '지도 편지'
},
{
type: 'bookmark',
label: '보관함'
}
];

return `${pathIndex * 100}%`;
};
const getTranslateX = (path: string) => {
const pathIndex =
{
keyword: 0,
map: 1,
bookmark: 2
}[path] || 0;

export const StoragePage = () => {
const navigate = useNavigate();
const { letterType } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
return `${pathIndex * 100}%`;
};

const handleNavigate = (type: LetterType) => {
const handleNavigate = (type: string) => {
if (type === 'bookmark') return navigate(`/storage/${type}`);
navigate(`/storage/${type}?filtertype=sent`);
};

// 마운트 될 때 초기 필터타입을 지정합니다
useEffect(() => {
if (letterType === 'bookmark') return;
if (selectedLetterType === 'bookmark') return;
const currentFilter = searchParams.get('filtertype');
if (!currentFilter) {
setSearchParams({ filtertype: 'sent' });
Expand All @@ -41,30 +53,22 @@ export const StoragePage = () => {
<div
className="absolute bottom-0 w-1/3 h-[2px] transition-transform duration-500 ease-in-out bg-sample-blue"
style={{
transform: `translateX(${getTranslateX(letterType as LetterType)})`
transform: `translateX(${getTranslateX(selectedLetterType as string)})`
}}
></div>
<div
className="flex items-center justify-center flex-1 h-full cursor-pointer"
onClick={() => handleNavigate('keyword')}
>
<span>키워드 편지</span>
</div>
<div
className="flex items-center justify-center flex-1 h-full cursor-pointer"
onClick={() => handleNavigate('map')}
>
<span>내 지도 편지</span>
</div>
<div
className="flex items-center justify-center flex-1 h-full cursor-pointer"
onClick={() => handleNavigate('bookmark')}
>
<span>보관함</span>
</div>
{storageMenuList.map((category) => {
return (
<div
key={category.type}
className="flex items-center justify-center flex-1 h-full cursor-pointer"
onClick={() => handleNavigate(category.type)}
>
<span>{category.label}</span>
</div>
);
})}
</div>
</div>

<div className="flex flex-col gap-2 mt-[60px]">
<StorageList />
</div>
Expand Down
3 changes: 1 addition & 2 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const ProfileSharePage = lazy(
import { tokenStorage } from './service/auth/tokenStorage';
import { AuthProvider } from './AuthProvider';
import { Container } from '@/components/Common/Container/Container';

import { CreateMapLetterPage } from './pages/Map/Create/CreateMapLetterPage';
import { MapSelectItemPage } from './pages/Map/Select/MapSelectItemPage';
import { ErrorBoundary } from './ErrorBoundary';
Expand Down Expand Up @@ -113,7 +112,7 @@ export const router = createBrowserRouter([
element: <MyPage />
},
{
path: 'storage/:letterType',
path: 'storage/:selectedLetterType',
element: <StoragePage />
},
{
Expand Down
Loading
Loading