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

토글버튼 부활, UI관련 수정 #557

Merged
merged 7 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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