Skip to content

Commit

Permalink
토글버튼 부활, UI관련 수정 (#557)
Browse files Browse the repository at this point in the history
* feat:데이로그 아이템 토글 기능 부활

* feat:이미지 클릭시 확대 모달창 여는 기능

* refactor: 지도 로딩시 스피너 중앙에 위치

* refactor: 이미지 사이즈 조정 로직 훅으로 분리

* refactor: 제목길이 25로 늘림

* refactor: api통신 횟수 조정, queryClient 함수 분리

* refactor: 여행설명, 아이템메모 사용자입력 줄바꿈 적용
  • Loading branch information
Dahyeeee authored Sep 12, 2023
1 parent ebbdaab commit b68af7a
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 79 deletions.
37 changes: 34 additions & 3 deletions frontend/src/components/common/DayLogItem/DayLogItem.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
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';
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;
Expand All @@ -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 (
<Box tag="section" css={containerStyling}>
<Flex css={headerStyling} styles={{ justify: 'space-between' }}>
Expand All @@ -28,12 +45,26 @@ const DayLogItem = ({
) : (
<Heading size="xSmall">{information.title}</Heading>
)}
{!isEditable && (
<ToggleGroup>
{[DAY_LOG_ITEM_FILTERS.ALL, DAY_LOG_ITEM_FILTERS.SPOT].map((filter) => (
<Toggle
key={filter}
text={filter}
toggleId={filter}
selectedId={selectedFilter}
changeSelect={handleFilterSelectClick}
aria-label={`${filter} 필터`}
/>
))}
</ToggleGroup>
)}
</Flex>
{information.items.length > 0 ? (
{selectedTripItemList.length > 0 ? (
<TripItemList
tripId={tripId}
dayLogId={information.id}
tripItems={information.items}
tripItems={selectedTripItemList}
isEditable={isEditable}
/>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const DayLogItemSkeleton = () => {
<Box css={containerStyling}>
<Flex css={headerStyling} styles={{ justify: 'space-between' }}>
<Skeleton width={isMobile ? '180px' : '250px'} height="38px" />
<Skeleton width="100px" height="38px" />
</Flex>
<TripItemListSkeleton />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Spinner />;
return (
<Flex
styles={{
width: '100%',
height: '100%',
justify: 'center',
align: 'center',
}}
>
<Spinner />
</Flex>
);
};

const GoogleMapWrapper = ({ children }: GoogleMapWrapperProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const titleStyling = css({

export const descriptionStyling = css({
marginTop: Theme.spacer.spacing3,

whiteSpace: 'pre-wrap',
});

export const buttonContainerStyling = css({
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/common/TripItem/TripItem.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const memoStyling = css({
marginTop: Theme.spacer.spacing3,

wordBreak: 'break-all',
whiteSpace: 'pre-wrap',
});

export const expenseStyling = css({
Expand Down
144 changes: 86 additions & 58 deletions frontend/src/components/common/TripItem/TripItem.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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<HTMLLIElement>(null);
Expand All @@ -66,61 +70,85 @@ const TripItem = ({
}, [observer]);

return (
<li
ref={itemRef}
css={getContainerStyling({ isEditable, isDragging })}
data-id={information.id}
draggable={isEditable}
onDragStart={onDragStart}
onDrag={isEditable ? handleDrag : undefined}
onDragEnter={onDragEnter}
onDragEnd={isEditable ? handleDragEnd : undefined}
>
<div ref={scrollRef} css={contentContainerStyling}>
{information.imageUrls.length > 0 && (
<ImageCarousel
width={isMobile ? imageWidth : 250}
height={isMobile ? imageHeight : 167}
isDraggable={false}
showNavigationOnHover={!isMobile}
showArrows={information.imageUrls.length > 1}
showDots={information.imageUrls.length > 1}
images={information.imageUrls}
/>
)}
<Box tag="section" css={informationContainerStyling}>
<Text size="large" css={titleStyling}>
{information.title}
</Text>
{information.place && (
<Text css={subInformationStyling} size="small">
{information.place.category.name}
</Text>
)}
{information.rating && <StarRating css={starRatingStyling} rate={information.rating} />}
{information.memo && (
<Text css={memoStyling} size="small">
{information.memo}
</Text>
<>
<li
ref={itemRef}
css={getContainerStyling({ isEditable, isDragging })}
data-id={information.id}
draggable={isEditable}
onDragStart={onDragStart}
onDrag={isEditable ? handleDrag : undefined}
onDragEnter={onDragEnter}
onDragEnd={isEditable ? handleDragEnd : undefined}
>
<div ref={scrollRef} css={contentContainerStyling}>
{information.imageUrls.length > 0 && (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
onClick={isMobile ? undefined : openImageModal}
onKeyDown={(e) => {
if (e.key === 'Enter') {
openImageModal();
}
}}
>
<ImageCarousel
width={isMobile ? mobileImageSize.width : TRIP_ITEM_IMAGE_WIDTH}
height={isMobile ? mobileImageSize.height : TRIP_ITEM_IMAGE_HEIGHT}
isDraggable={false}
showNavigationOnHover={!isMobile}
showArrows={information.imageUrls.length > 1}
showDots={information.imageUrls.length > 1}
images={information.imageUrls}
/>
</div>
)}
{information.expense && (
<Text css={expenseStyling} size="small">
{information.expense.category.name} · {CURRENCY_ICON[information.expense.currency]}
{formatNumberToMoney(information.expense.amount)}
<Box tag="section" css={informationContainerStyling}>
<Text size="large" css={titleStyling}>
{information.title}
</Text>
)}
</Box>
</div>
{isEditable ? (
<EditMenu
tripId={tripId}
dayLogId={dayLogId}
hasImage={information.imageUrls.length > 0}
imageHeight={imageHeight}
{...information}
{information.place && (
<Text css={subInformationStyling} size="small">
{information.place.category.name}
</Text>
)}
{information.rating && <StarRating css={starRatingStyling} rate={information.rating} />}
{information.memo && (
<Text css={memoStyling} size="small">
{information.memo}
</Text>
)}
{information.expense && (
<Text css={expenseStyling} size="small">
{information.expense.category.name} · {CURRENCY_ICON[information.expense.currency]}
{formatNumberToMoney(information.expense.amount)}
</Text>
)}
</Box>
</div>
{isEditable ? (
<EditMenu
tripId={tripId}
dayLogId={dayLogId}
hasImage={information.imageUrls.length > 0}
imageHeight={mobileImageSize.height}
{...information}
/>
) : null}
</li>
<Modal isOpen={isImageModalOpen} closeModal={closeImageModal}>
<Heading size="small">{information.title}</Heading>
<ImageCarousel
width={modalImageSize.width}
height={modalImageSize.height}
isDraggable={false}
showNavigationOnHover={!isMobile}
showArrows={information.imageUrls.length > 1}
showDots={information.imageUrls.length > 1}
images={information.imageUrls}
/>
) : null}
</li>
</Modal>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const END_POINTS = {
} as const;

export const NETWORK = {
RETRY_COUNT: 3,
RETRY_COUNT: 2,
TIMEOUT: 10000,
} as const;

Expand Down
8 changes: 6 additions & 2 deletions frontend/src/constants/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
13 changes: 13 additions & 0 deletions frontend/src/hooks/api/queryClient.ts
Original file line number Diff line number Diff line change
@@ -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,
},
},
});
38 changes: 38 additions & 0 deletions frontend/src/hooks/trip/useResizeImage.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit b68af7a

Please sign in to comment.