diff --git a/src/assets/Location.svg b/src/assets/Location.svg new file mode 100644 index 00000000..98546374 --- /dev/null +++ b/src/assets/Location.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/rightArrow.svg b/src/assets/rightArrow.svg new file mode 100644 index 00000000..66015e0c --- /dev/null +++ b/src/assets/rightArrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Common/LetterListItem/Category.tsx b/src/components/Common/LetterListItem/Category.tsx new file mode 100644 index 00000000..d7801dda --- /dev/null +++ b/src/components/Common/LetterListItem/Category.tsx @@ -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
보낸 편지
; + case 'SEND-REPLY_LETTER': + return
보낸 답장
; + case 'RECEIVE-LETTER': + return
받은 편지
; + case 'RECEIVE-REPLY_LETTER': + return
받은 답장
; + default: + } +}; diff --git a/src/components/StoragePage/LetterDateGroup.tsx b/src/components/StoragePage/LetterDateGroup.tsx index b5a99cde..944fbb7a 100644 --- a/src/components/StoragePage/LetterDateGroup.tsx +++ b/src/components/StoragePage/LetterDateGroup.tsx @@ -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, diff --git a/src/components/StoragePage/LetterListItem.tsx b/src/components/StoragePage/LetterListItem.tsx index df0ed9c4..662050eb 100644 --- a/src/components/StoragePage/LetterListItem.tsx +++ b/src/components/StoragePage/LetterListItem.tsx @@ -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, @@ -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(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 ( + <> + +

+ {keywordLetter.title} +

+ + ); + } + 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 ( + <> +
+ {getUserNicknameLabel(mapLetter)} +
+
+

+ {letter.title} +

+
+
+ +
+ 서울특별시 동대문구 어디어디 +
+
+ + ); + } + case 'bookmark': { + const bookmarkLetter = letter as StorageMapArchivedLetter; + return ( + <> +

+ {bookmarkLetter.title} +

+
샘플 위치가 들어갑니다.
+ + ); + } + } }; return ( @@ -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))} > - - - -
-
- {renderCategory(letter.boxType, letter.letterType)} +
+
+ +
+
+ {renderListContent()}
-

{letter.title}

+
); diff --git a/src/components/StoragePage/StorageList.tsx b/src/components/StoragePage/StorageList.tsx index 435a0c75..0c7ee77a 100644 --- a/src/components/StoragePage/StorageList.tsx +++ b/src/components/StoragePage/StorageList.tsx @@ -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(); @@ -24,7 +24,7 @@ export const StorageList = () => { const [checkedItems, setCheckedItems] = useState([]); const getApiEndpoint = () => { - return match(letterType as storageLetterType) + return match(selectedLetterType as storageLetterType) .with('keyword', () => `/letters/saved/${filterType}`) .with('map', () => `/map/${filterType}`) .with('bookmark', () => '/map/archived') diff --git a/src/hooks/useInfiniteStorageFetch.ts b/src/hooks/useInfiniteStorageFetch.ts index 086d7025..5442adcd 100644 --- a/src/hooks/useInfiniteStorageFetch.ts +++ b/src/hooks/useInfiniteStorageFetch.ts @@ -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; @@ -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]; diff --git a/src/pages/Storage/StoragePage.tsx b/src/pages/Storage/StoragePage.tsx index 40cda7be..786fee1b 100644 --- a/src/pages/Storage/StoragePage.tsx +++ b/src/pages/Storage/StoragePage.tsx @@ -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' }); @@ -41,30 +53,22 @@ export const StoragePage = () => {
-
handleNavigate('keyword')} - > - 키워드 편지 -
-
handleNavigate('map')} - > - 내 지도 편지 -
-
handleNavigate('bookmark')} - > - 보관함 -
+ {storageMenuList.map((category) => { + return ( +
handleNavigate(category.type)} + > + {category.label} +
+ ); + })} -
diff --git a/src/router.tsx b/src/router.tsx index 79720abf..306367f1 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -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'; @@ -113,7 +112,7 @@ export const router = createBrowserRouter([ element: }, { - path: 'storage/:letterType', + path: 'storage/:selectedLetterType', element: }, { diff --git a/src/service/storage/getLetter.ts b/src/service/storage/getLetter.ts index 0b10cfd9..db93124e 100644 --- a/src/service/storage/getLetter.ts +++ b/src/service/storage/getLetter.ts @@ -1,14 +1,6 @@ import { defaultApi } from '@/service/api'; import { ApiResponseType } from '@/types/apiResponse'; - -type Letter = { - letterId: number; - title: string; - label: string; - letterType: string; - boxType: string; - createdAt: string; -}; +import { StorageLetterDataType } from '@/types/letter'; type getLetterProps = { apiEndpoint: string; @@ -18,7 +10,7 @@ type getLetterProps = { }; type getLetterResultType = { - content: Letter[]; + content: StorageLetterDataType[]; page: number; size: number; totalElements: number; diff --git a/src/types/letter.ts b/src/types/letter.ts index 05b99ebd..3712576e 100644 --- a/src/types/letter.ts +++ b/src/types/letter.ts @@ -8,6 +8,8 @@ export type LetterType = { }; export type storageLetterType = 'keyword' | 'map' | 'bookmark'; +export type ApiBoxType = 'SEND' | 'RECEIVE'; +export type ApiLetterType = 'LETTER' | 'REPLY_LETTER'; export type DeleteLetterType = { letterId: number; @@ -15,33 +17,49 @@ export type DeleteLetterType = { boxType: string; }; -export type StorageKeywordLetterType = { +export interface BaseLetter { letterId: number; title: string; - description: string; - latitude: number; - longitude: number; label: string; + letterType: ApiLetterType; + boxType: ApiBoxType; createdAt: string; +} + +export interface StorageKeywordLetter extends BaseLetter { + keywords?: string[]; // TODO : 키워드 관련 특수 필드 요청 +} + +export interface StorageMapSentLetter extends BaseLetter { + description: string; + targetUserNickname: string; type: string; sourceLetterId: number; - senderNickname: string; - senderProfileImg: string; -}; +} -export type StorageMapLetterType = { - letterId: number; - title: string; +export interface StorageMapReceivedLetter extends BaseLetter { description: string; latitude: number; longitude: number; - label: string; - createdAt: string; type: string; sourceLetterId: number; senderNickname: string; senderProfileImg: string; -}; +} + +export interface StorageMapArchivedLetter extends BaseLetter { + archiveId: number; + description: string; + latitude: number; + longitude: number; + letterCreatedAt: string; +} + +export type StorageLetterDataType = + | StorageKeywordLetter + | StorageMapSentLetter + | StorageMapReceivedLetter + | StorageMapArchivedLetter; export type MapReplyType = { sourceLetter: number;