Skip to content

Commit

Permalink
feat : 보관함 삭제 기능 api 수정 (#493)
Browse files Browse the repository at this point in the history
* chore : 잘못 작성된 버튼 위치 수정

* refactor : 삭제 모달 컨텐츠 분리

* feat : 삭제 api를 편지 종류별로 분리

* Delete deleteLetters.ts

* refactor : 삭제 모달 컨텐츠 분리 & handleRefresh 추가

* chore : 401 에러 디버깅용 콘솔출력/retry설정

* chore : 레터타입 아카이브에서 토글메뉴 삭제

* design : 보관함 상단 토글 z-index 수정

* refactor : handleDelete 함수 부모 컴포넌트로 이동

* chore : 삭제 관련 타입 수정

* feat : 아카이브 편지 삭제 함수 추가

* chore : 지도편지 삭제 타입 변경

* refactor : 삭제 대기 체크 리스트에서 편지 데이터를 모두 가지고 있도록 변경

* fix : 잘못 작성된 아카이브 편지 삭제 요청 배열을 아카이브 아이디 배열로 변경
  • Loading branch information
cmlim0070 authored Feb 3, 2025
1 parent 5a81b7e commit ee899b7
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 101 deletions.
30 changes: 30 additions & 0 deletions src/components/StoragePage/DeleteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { useModal } from '@/hooks';

type DeleteModalProps = {
handleDelete: () => void;
};

export const DeleteModal = ({ handleDelete }: DeleteModalProps) => {
const { closeModal } = useModal();

return (
<div className="flex flex-col items-center justify-center w-full h-full gap-3">
<div className="text-bold">정말 삭제하시겠습니까?</div>
<div className="flex flex-row items-center justify-center w-full gap-1">
<button
onClick={handleDelete}
className="px-3 py-1 text-white rounded-sm bg-sample-blue"
>
</button>
<button
onClick={closeModal}
className="px-3 py-1 bg-white border rounded-sm border-sample-blue text-sample-blue"
>
아니오
</button>
</div>
</div>
);
};
6 changes: 3 additions & 3 deletions src/components/StoragePage/LetterDateGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { LetterListItem } from './LetterListItem';
import { DeleteLetterType, StorageLetterDataType } from '@/types/letter';
import { StorageLetterDataType } from '@/types/letter';

type LetterDateGroupProps = {
date: string;
letters: StorageLetterDataType[];
checkedItems: DeleteLetterType[];
checkedItems: StorageLetterDataType[];
handleSingleCheck: (
checked: boolean,
{ letterId, letterType, boxType }: DeleteLetterType
letter: StorageLetterDataType
) => void;
};

Expand Down
11 changes: 2 additions & 9 deletions src/components/StoragePage/LetterListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BottleLetter } from '@/components/Common/BottleLetter/BottleLetter';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import {
DeleteLetterType,
StorageKeywordLetter,
StorageLetterDataType,
StorageMapArchivedLetter,
Expand All @@ -22,7 +21,7 @@ type LetterListItemProps = {
checked: boolean;
handleSingleCheck: (
checked: boolean,
{ letterId, letterType, boxType }: DeleteLetterType
letter: StorageLetterDataType
) => void;
};

Expand Down Expand Up @@ -124,13 +123,7 @@ export const LetterListItem = ({
<input
type="checkbox"
name={`select-${letter.letterId}`}
onChange={(e) =>
handleSingleCheck(e.target.checked, {
letterId: letter.letterId,
letterType: letter.letterType,
boxType: letter.boxType
})
}
onChange={(e) => handleSingleCheck(e.target.checked, letter)}
checked={checked}
/>
<div
Expand Down
175 changes: 102 additions & 73 deletions src/components/StoragePage/StorageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import React, { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useInView } from 'react-intersection-observer';
import { useInfiniteStorageFetch } from '@/hooks/useInfiniteStorageFetch';
import { useQueryClient } from '@tanstack/react-query';
import { match } from 'ts-pattern';
import { deleteLetters } from '@/service/letter/delete/deleteLetters';
import { useModal, useToastStore } from '@/hooks';
import { DeleteLetterType, storageLetterType } from '@/types/letter';
import {
DeleteKeywordLetterType,
StorageLetterDataType,
storageLetterType,
StorageMapArchivedLetter
} from '@/types/letter';
import { Empty } from '@/components/Common/Empty/Empty';
import { Loading } from '@/components/Common/Loading/Loading';
import { LetterDateGroup } from './LetterDateGroup';
import { DeleteModal } from './DeleteModal';
import { useQueryClient } from '@tanstack/react-query';
import { deleteKeywordLetters } from '@/service/letter/delete/deleteKeywordLetters';
import { deleteMapLetters } from '@/service/letter/delete/deleteMapLetters';
import { deleteBookmarkLetters } from '@/service/letter/delete/deleteBookmarkLetters';

const ROWS_PER_PAGE = 10;

Expand All @@ -20,13 +28,19 @@ export const StorageList = () => {
const filterType = searchParams.get('filtertype');
const { ref, inView } = useInView();
const { addToast } = useToastStore();
const { openModal, closeModal, ModalComponent } = useModal();
const [checkedItems, setCheckedItems] = useState<DeleteLetterType[]>([]);
const { openModal, ModalComponent, closeModal } = useModal();
const [checkedItems, setCheckedItems] = useState<StorageLetterDataType[]>(
[]
);

const getApiEndpoint = () => {
return match<storageLetterType>(selectedLetterType as storageLetterType)
.with('keyword', () => `/letters/saved/${filterType}`)
.with('map', () => `/map/${filterType}`)
.with('map', () => {
const mapType =
filterType === 'received' ? 'received-target' : 'sent-map';
return `/map/saved?type=${mapType}`;
})
.with('bookmark', () => '/map/archived')
.exhaustive();
};
Expand All @@ -44,57 +58,84 @@ export const StorageList = () => {

const handleSingleCheck = (
checked: boolean,
{ letterId, letterType, boxType }: DeleteLetterType
letter: StorageLetterDataType
) => {
if (checked) {
setCheckedItems((prev) => [
...prev,
{ letterId, letterType, boxType }
]);
setCheckedItems((prev) => [...prev, letter]);
} else {
setCheckedItems(
checkedItems.filter((item) => item.letterId !== letterId)
checkedItems.filter((item) => item.letterId !== letter.letterId)
);
}
console.log(checkedItems);
};

const handleAllCheck = (checked: boolean) => {
if (checked) {
const itemArray: {
letterId: number;
letterType: string;
boxType: string;
}[] = [];
groupedLetters.forEach((item) => {
item.letters.forEach((letter) => {
itemArray.push({
letterId: letter.letterId,
letterType: letter.letterType,
boxType: letter.boxType
});
});
});
const itemArray = groupedLetters.flatMap((group) => group.letters);
setCheckedItems(itemArray);
} else {
setCheckedItems([]);
}
};

const handleDelete = async () => {
const response = await deleteLetters(checkedItems);
if (response.isSuccess) {
let response;
switch (selectedLetterType) {
case 'keyword': {
const keywordPayload = checkedItems.map((item) => ({
letterId: item.letterId,
letterType: item.letterType,
boxType: item.boxType
})) as DeleteKeywordLetterType[];
response = await deleteKeywordLetters(keywordPayload);
break;
}
case 'map': {
const mapPayload = {
letterIds: checkedItems.map((item) => item.letterId)
};
response = await deleteMapLetters(mapPayload);
break;
}
case 'bookmark': {
const archivedLetters = checkedItems.filter(
(item): item is StorageMapArchivedLetter =>
'archiveIds' in item
);
const bookmarkPayload = {
archiveIds: archivedLetters.map((item) => item.archiveId)
};
response = await deleteBookmarkLetters(bookmarkPayload);
break;
}
}

if (response?.isSuccess) {
addToast('삭제가 완료되었습니다.', 'success');
setCheckedItems([]);
queryClient.invalidateQueries({
queryKey: ['storageLetters', getApiEndpoint()]
});
handleRefresh();
closeModal();
return;
}

addToast('삭제에 실패했습니다.', 'warning');
return;
};

const handleRefresh = () => {
console.log('리프레시');
setCheckedItems([]);
queryClient.invalidateQueries({
queryKey: ['storageLetters', getApiEndpoint()]
});
queryClient.invalidateQueries({
queryKey: ['recommendedLetter']
});
queryClient.invalidateQueries({
queryKey: ['keywordLetterDetail']
});
};

useEffect(() => {
if (inView) {
fetchNextPage();
Expand All @@ -113,23 +154,7 @@ export const StorageList = () => {
return (
<div className="">
<ModalComponent height="h-[200px] w-[250px]">
<div className="flex flex-col items-center justify-center w-full h-full gap-3">
<div className="text-bold">정말 삭제하시겠습니까?</div>
<div className="flex flex-row items-center justify-center w-full gap-1">
<button
onClick={handleDelete}
className="px-3 py-1 text-white rounded-sm bg-sample-blue"
>
</button>
<button
onClick={closeModal}
className="px-3 py-1 bg-white border rounded-sm border-sample-blue text-sample-blue"
>
아니오
</button>
</div>
</div>
<DeleteModal handleDelete={handleDelete} />
</ModalComponent>
{groupedLetters.map((dayGroup) => (
<LetterDateGroup
Expand All @@ -146,30 +171,34 @@ export const StorageList = () => {

return (
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">
<button
className={`border border-sample-blue rounded-xl text-sm px-2 py-1
${
filterType === 'sent'
? 'bg-sample-blue text-white'
: 'bg-white text-sample-blue'
}`}
onClick={() => setSearchParams({ filtertype: 'sent' })}
>
보낸 편지
</button>
<button
className={`border border-sample-blue rounded-xl text-sm px-2 py-1
${
filterType === 'received'
? 'bg-sample-blue text-white'
: 'bg-white text-sample-blue'
}`}
onClick={() => setSearchParams({ filtertype: 'received' })}
>
받은 편지
</button>
</div>
{selectedLetterType !== 'bookmark' && (
<div className="flex flex-row gap-2">
<button
className={`border border-sample-blue rounded-xl text-sm px-2 py-1
${
filterType === 'sent'
? 'bg-sample-blue text-white'
: 'bg-white text-sample-blue'
}`}
onClick={() => setSearchParams({ filtertype: 'sent' })}
>
보낸 편지
</button>
<button
className={`border border-sample-blue rounded-xl text-sm px-2 py-1
${
filterType === 'received'
? 'bg-sample-blue text-white'
: 'bg-white text-sample-blue'
}`}
onClick={() =>
setSearchParams({ filtertype: 'received' })
}
>
받은 편지
</button>
</div>
)}
{groupedLetters.length === 0 ? null : (
<div className="flex flex-row justify-between w-full gap-3 text-sm">
<div className="flex flex-row items-center gap-1">
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useGetKeywordLetterDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const useKeywordLetterDetail = ({
queryFn: async () => {
const response = await getKeywordLetterDetail({ letterId });
return response.result;
}
},
retry: false
});
};
2 changes: 1 addition & 1 deletion src/pages/Storage/StoragePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const StoragePage = () => {

return (
<div className="flex flex-col h-full">
<div className="max-w-[473px] fixed min-w-[375px] w-full top-0 z-[9999] bg-white -ml-5">
<div className="max-w-[473px] fixed min-w-[375px] w-full top-0 z-[40] bg-white -ml-5">
<div className="relative flex w-full overflow-hidden text-md align-middle h-[50px] text-sample-black">
<div
className="absolute bottom-0 w-1/3 h-[2px] transition-transform duration-500 ease-in-out bg-sample-blue"
Expand Down
16 changes: 7 additions & 9 deletions src/pages/User/Register/RegisterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,16 @@ export const RegisterPage = () => {
isNicknameChecked={validationState.isNicknameChecked}
isNicknameValid={validationState.isNicknameValid}
></NicknameSection>
</form>
<div className="flex flex-col w-full gap-2">
<button type="submit" className="btn-primary-filled">
가입하기
</button>
<div>
이미 회원이신가요?
<Link to="/login" className="text-bold text-sample-blue">
{' '}
로그인
</Link>
</div>
</form>
<div>
이미 회원이신가요?
<Link to="/login" className="text-bold text-sample-blue">
{' '}
로그인
</Link>
</div>
</div>
);
Expand Down
18 changes: 18 additions & 0 deletions src/service/letter/delete/deleteBookmarkLetters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defaultApi } from '@/service/api';
import { ApiResponseType } from '@/types/apiResponse';

type DeleteLetterType = {
archiveIds: number[];
};

type deleteBookmarkLettersResponse = ApiResponseType<string>;

export const deleteBookmarkLetters = async (
selectedList: DeleteLetterType
): Promise<deleteBookmarkLettersResponse> => {
const api = defaultApi();
const response = await api.delete('/map/archived', {
data: selectedList
});
return response.data;
};
Loading

0 comments on commit ee899b7

Please sign in to comment.