From b68af7aec8f6d2d74ccdaef4c68563391a74698d Mon Sep 17 00:00:00 2001 From: Dahye Yun <102305630+Dahyeeee@users.noreply.github.com> Date: Tue, 12 Sep 2023 10:49:18 +0900 Subject: [PATCH] =?UTF-8?q?=ED=86=A0=EA=B8=80=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EB=B6=80=ED=99=9C,=20UI=EA=B4=80=EB=A0=A8=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=20(#557)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:데이로그 아이템 토글 기능 부활 * feat:이미지 클릭시 확대 모달창 여는 기능 * refactor: 지도 로딩시 스피너 중앙에 위치 * refactor: 이미지 사이즈 조정 로직 훅으로 분리 * refactor: 제목길이 25로 늘림 * refactor: api통신 횟수 조정, queryClient 함수 분리 * refactor: 여행설명, 아이템메모 사용자입력 줄바꿈 적용 --- .../common/DayLogItem/DayLogItem.tsx | 37 ++++- .../common/DayLogItem/DayLogItemSkeleton.tsx | 1 + .../GoogleMapWrapper/GoogleMapWrapper.tsx | 15 +- .../TripInformation/TripInformation.style.ts | 2 + .../common/TripItem/TripItem.style.ts | 1 + .../components/common/TripItem/TripItem.tsx | 144 +++++++++++------- frontend/src/constants/api.ts | 2 +- frontend/src/constants/ui.ts | 8 +- frontend/src/hooks/api/queryClient.ts | 13 ++ frontend/src/hooks/trip/useResizeImage.ts | 38 +++++ frontend/src/index.tsx | 16 +- 11 files changed, 198 insertions(+), 79 deletions(-) create mode 100644 frontend/src/hooks/api/queryClient.ts create mode 100644 frontend/src/hooks/trip/useResizeImage.ts diff --git a/frontend/src/components/common/DayLogItem/DayLogItem.tsx b/frontend/src/components/common/DayLogItem/DayLogItem.tsx index 759703fb2..0d82c3be5 100644 --- a/frontend/src/components/common/DayLogItem/DayLogItem.tsx +++ b/frontend/src/components/common/DayLogItem/DayLogItem.tsx @@ -1,4 +1,6 @@ -import { Box, Flex, Heading } from 'hang-log-design-system'; +import { useEffect } from 'react'; + +import { Box, Flex, Heading, Toggle, ToggleGroup, useSelect } from 'hang-log-design-system'; import { containerStyling, headerStyling } from '@components/common/DayLogItem/DayLogItem.style'; import TitleInput from '@components/common/DayLogItem/TitleInput/TitleInput'; @@ -6,6 +8,8 @@ import TripItemList from '@components/common/TripItemList/TripItemList'; import type { DayLogData } from '@type/dayLog'; +import { DAY_LOG_ITEM_FILTERS } from '@constants/trip'; + interface DayLogItemProps extends DayLogData { tripId: number; isEditable?: boolean; @@ -20,6 +24,19 @@ const DayLogItem = ({ openAddModal, ...information }: DayLogItemProps) => { + const { selected: selectedFilter, handleSelectClick: handleFilterSelectClick } = useSelect( + DAY_LOG_ITEM_FILTERS.ALL + ); + + const selectedTripItemList = + selectedFilter === DAY_LOG_ITEM_FILTERS.SPOT + ? information.items.filter((item) => item.itemType === true) + : information.items; + + useEffect(() => { + handleFilterSelectClick(DAY_LOG_ITEM_FILTERS.ALL); + }, [handleFilterSelectClick, information.items]); + return ( @@ -28,12 +45,26 @@ const DayLogItem = ({ ) : ( {information.title} )} + {!isEditable && ( + + {[DAY_LOG_ITEM_FILTERS.ALL, DAY_LOG_ITEM_FILTERS.SPOT].map((filter) => ( + + ))} + + )} - {information.items.length > 0 ? ( + {selectedTripItemList.length > 0 ? ( ) : ( diff --git a/frontend/src/components/common/DayLogItem/DayLogItemSkeleton.tsx b/frontend/src/components/common/DayLogItem/DayLogItemSkeleton.tsx index d7ff79752..5d6adbe9b 100644 --- a/frontend/src/components/common/DayLogItem/DayLogItemSkeleton.tsx +++ b/frontend/src/components/common/DayLogItem/DayLogItemSkeleton.tsx @@ -14,6 +14,7 @@ const DayLogItemSkeleton = () => { + diff --git a/frontend/src/components/common/GoogleMapWrapper/GoogleMapWrapper.tsx b/frontend/src/components/common/GoogleMapWrapper/GoogleMapWrapper.tsx index 0c39cbf82..361693777 100644 --- a/frontend/src/components/common/GoogleMapWrapper/GoogleMapWrapper.tsx +++ b/frontend/src/components/common/GoogleMapWrapper/GoogleMapWrapper.tsx @@ -2,14 +2,25 @@ import { Status, Wrapper } from '@googlemaps/react-wrapper'; import type { PropsWithChildren } from 'react'; -import { Spinner } from 'hang-log-design-system'; +import { Flex, Spinner } from 'hang-log-design-system'; type GoogleMapWrapperProps = PropsWithChildren; const render = (status: Status) => { if (status === Status.FAILURE) throw new Error('오류가 발생했습니다.'); - return ; + return ( + + + + ); }; const GoogleMapWrapper = ({ children }: GoogleMapWrapperProps) => { diff --git a/frontend/src/components/common/TripInformation/TripInformation.style.ts b/frontend/src/components/common/TripInformation/TripInformation.style.ts index 737f27b96..3991a49ba 100644 --- a/frontend/src/components/common/TripInformation/TripInformation.style.ts +++ b/frontend/src/components/common/TripInformation/TripInformation.style.ts @@ -51,6 +51,8 @@ export const titleStyling = css({ export const descriptionStyling = css({ marginTop: Theme.spacer.spacing3, + + whiteSpace: 'pre-wrap', }); export const buttonContainerStyling = css({ diff --git a/frontend/src/components/common/TripItem/TripItem.style.ts b/frontend/src/components/common/TripItem/TripItem.style.ts index 1551df0b8..e854315ea 100644 --- a/frontend/src/components/common/TripItem/TripItem.style.ts +++ b/frontend/src/components/common/TripItem/TripItem.style.ts @@ -57,6 +57,7 @@ export const memoStyling = css({ marginTop: Theme.spacer.spacing3, wordBreak: 'break-all', + whiteSpace: 'pre-wrap', }); export const expenseStyling = css({ diff --git a/frontend/src/components/common/TripItem/TripItem.tsx b/frontend/src/components/common/TripItem/TripItem.tsx index 98064c438..4a8da4ce8 100644 --- a/frontend/src/components/common/TripItem/TripItem.tsx +++ b/frontend/src/components/common/TripItem/TripItem.tsx @@ -1,9 +1,9 @@ import type { ForwardedRef } from 'react'; -import { useEffect, useMemo, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import { useRecoilValue } from 'recoil'; -import { Box, ImageCarousel, Text } from 'hang-log-design-system'; +import { Box, Heading, ImageCarousel, Modal, Text, useOverlay } from 'hang-log-design-system'; import StarRating from '@components/common/StarRating/StarRating'; import EditMenu from '@components/common/TripItem/EditMenu/EditMenu'; @@ -19,14 +19,16 @@ import { } from '@components/common/TripItem/TripItem.style'; import { useDraggedItem } from '@hooks/common/useDraggedItem'; +import useResizeImage from '@hooks/trip/useResizeImage'; -import { mediaQueryMobileState, viewportWidthState } from '@store/mediaQuery'; +import { mediaQueryMobileState } from '@store/mediaQuery'; import { formatNumberToMoney } from '@utils/formatter'; import type { TripItemData } from '@type/tripItem'; import { CURRENCY_ICON } from '@constants/trip'; +import { TRIP_ITEM_IMAGE_HEIGHT, TRIP_ITEM_IMAGE_WIDTH } from '@constants/ui'; interface TripListItemProps extends TripItemData { tripId: number; @@ -51,10 +53,12 @@ const TripItem = ({ ...information }: TripListItemProps) => { const isMobile = useRecoilValue(mediaQueryMobileState); - const viewportWidth = useRecoilValue(viewportWidthState); + const { mobileImageSize, modalImageSize } = useResizeImage({ + width: TRIP_ITEM_IMAGE_WIDTH, + height: TRIP_ITEM_IMAGE_HEIGHT, + }); - const imageWidth = useMemo(() => viewportWidth - 48, [viewportWidth]); - const imageHeight = useMemo(() => (imageWidth / 4.5) * 3, [imageWidth]); + const { isOpen: isImageModalOpen, open: openImageModal, close: closeImageModal } = useOverlay(); const { isDragging, handleDrag, handleDragEnd } = useDraggedItem(onDragEnd); const itemRef = useRef(null); @@ -66,61 +70,85 @@ const TripItem = ({ }, [observer]); return ( -
  • -
    - {information.imageUrls.length > 0 && ( - 1} - showDots={information.imageUrls.length > 1} - images={information.imageUrls} - /> - )} - - - {information.title} - - {information.place && ( - - {information.place.category.name} - - )} - {information.rating && } - {information.memo && ( - - {information.memo} - + <> +
  • +
    + {information.imageUrls.length > 0 && ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
    { + if (e.key === 'Enter') { + openImageModal(); + } + }} + > + 1} + showDots={information.imageUrls.length > 1} + images={information.imageUrls} + /> +
    )} - {information.expense && ( - - {information.expense.category.name} · {CURRENCY_ICON[information.expense.currency]} - {formatNumberToMoney(information.expense.amount)} + + + {information.title} - )} - -
    - {isEditable ? ( - 0} - imageHeight={imageHeight} - {...information} + {information.place && ( + + {information.place.category.name} + + )} + {information.rating && } + {information.memo && ( + + {information.memo} + + )} + {information.expense && ( + + {information.expense.category.name} · {CURRENCY_ICON[information.expense.currency]} + {formatNumberToMoney(information.expense.amount)} + + )} + + + {isEditable ? ( + 0} + imageHeight={mobileImageSize.height} + {...information} + /> + ) : null} +
  • + + {information.title} + 1} + showDots={information.imageUrls.length > 1} + images={information.imageUrls} /> - ) : null} - + + ); }; diff --git a/frontend/src/constants/api.ts b/frontend/src/constants/api.ts index b6a5e29f3..2f655f752 100644 --- a/frontend/src/constants/api.ts +++ b/frontend/src/constants/api.ts @@ -29,7 +29,7 @@ export const END_POINTS = { } as const; export const NETWORK = { - RETRY_COUNT: 3, + RETRY_COUNT: 2, TIMEOUT: 10000, } as const; diff --git a/frontend/src/constants/ui.ts b/frontend/src/constants/ui.ts index 05e27574d..0f86c9a93 100644 --- a/frontend/src/constants/ui.ts +++ b/frontend/src/constants/ui.ts @@ -10,13 +10,13 @@ export const EXPENSE_CATEGORY_INFORMATION_SKELETON_LENGTH = 6; export const EXPENSE_LIST_SKELETON_LENGTH = 5; -export const TRIP_TITLE_MAX_LENGTH = 14; +export const TRIP_TITLE_MAX_LENGTH = 24; export const TRIP_DESCRIPTION_MAX_LENGTH = 124; export const DAYLOG_TITLE_MAX_LENGTH = 24; -export const TRIP_ITEM_TITLE_MAX_LENGTH = 20; +export const TRIP_ITEM_TITLE_MAX_LENGTH = 24; export const TRIP_ITEM_MEMO_MAX_LENGTH = 254; @@ -29,3 +29,7 @@ export const AMOUNT_MAX_LIMIT = 100_000_000; export const EXPENSE_CATEGORY_CHART_SIZE = 300; export const EXPENSE_CATEGORY_CHART_STROKE_WIDTH = 60; + +export const TRIP_ITEM_IMAGE_WIDTH = 250; + +export const TRIP_ITEM_IMAGE_HEIGHT = 167; diff --git a/frontend/src/hooks/api/queryClient.ts b/frontend/src/hooks/api/queryClient.ts new file mode 100644 index 000000000..a08c0201d --- /dev/null +++ b/frontend/src/hooks/api/queryClient.ts @@ -0,0 +1,13 @@ +import { QueryClient } from '@tanstack/react-query'; + +import { NETWORK } from '@constants/api'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: NETWORK.RETRY_COUNT, + suspense: true, + useErrorBoundary: true, + }, + }, +}); diff --git a/frontend/src/hooks/trip/useResizeImage.ts b/frontend/src/hooks/trip/useResizeImage.ts new file mode 100644 index 000000000..a57ecf090 --- /dev/null +++ b/frontend/src/hooks/trip/useResizeImage.ts @@ -0,0 +1,38 @@ +import { viewportWidthState } from '@/store/mediaQuery'; + +import { useMemo } from 'react'; + +import { useRecoilValue } from 'recoil'; + +interface useResizeImageParam { + width?: number; + height?: number; +} + +interface mobileImageSizeType { + mobileImageSize: { width: number; height: number }; + modalImageSize: { width: number; height: number }; +} + +const useResizeImage = ({ width = 0, height = 0 }: useResizeImageParam): mobileImageSizeType => { + const viewportWidth = useRecoilValue(viewportWidthState); + + const mobileImageWidth = useMemo(() => viewportWidth - 48, [viewportWidth]); + const mobileImageHeight = useMemo(() => (mobileImageWidth / 4.5) * 3, [mobileImageWidth]); + + const modalImageWidth = useMemo(() => width * 2, [width]); + const modalImageHeight = useMemo(() => height * 2, [height]); + + return { + mobileImageSize: { + height: mobileImageHeight, + width: mobileImageWidth, + }, + modalImageSize: { + height: modalImageHeight, + width: modalImageWidth, + }, + }; +}; + +export default useResizeImage; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 679d76b43..b5ed18ae5 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -3,7 +3,7 @@ import { Global } from '@emotion/react'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { RecoilRoot } from 'recoil'; @@ -12,9 +12,9 @@ import { HangLogProvider } from 'hang-log-design-system'; import HttpsRedirect from '@components/utils/HttpsRedirect'; -import AppRouter from '@router/AppRouter'; +import { queryClient } from '@hooks/api/queryClient'; -import { NETWORK } from '@constants/api'; +import AppRouter from '@router/AppRouter'; import { GlobalStyle } from '@styles/index'; @@ -32,16 +32,6 @@ const main = async () => { const root = createRoot(document.querySelector('#root') as Element); - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: NETWORK.RETRY_COUNT, - suspense: true, - useErrorBoundary: true, - }, - }, - }); - root.render(