Skip to content
Open
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
25 changes: 25 additions & 0 deletions apps/client/src/pages/memo/components/memo-list/memo-list.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { style } from '@vanilla-extract/css';

const THREE_COLUMN_MIN_WIDTH = '1060px';
const MEMO_LIST_CONTAINER = 'memoList';

const columns = (count: number) => `repeat(${count}, minmax(34rem, 38rem))`;

export const memoListContainer = style({
width: '100%',
containerType: 'inline-size',
containerName: MEMO_LIST_CONTAINER,
});

export const memoListGrid = style({
display: 'grid',
gridTemplateColumns: columns(2),
justifyContent: 'safe center',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것은 뭔밍 정말 처음봐요 안전한 센터 참 신기하네요

gap: '2rem',

'@container': {
[`${MEMO_LIST_CONTAINER} (min-width: ${THREE_COLUMN_MIN_WIDTH})`]: {
gridTemplateColumns: columns(3),
},
},
});
45 changes: 45 additions & 0 deletions apps/client/src/pages/memo/components/memo-list/memo-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Card from '@shared/components/card/card';
import { components } from '@shared/types/schema';

import * as styles from './memo-list.css';

type CardType = components['schemas']['MemoDashboardResponse'];

interface MemoListProps {
cards: CardType[];
isSelected: boolean;
isDragging: boolean;
onClickCard: () => void;
}

const MemoList = ({
cards,
isSelected,
isDragging,
onClickCard,
}: MemoListProps) => {
return (
<div className={styles.memoListContainer}>
<div className={styles.memoListGrid}>
{cards.map((card) => (
<Card
key={card.memoId}
card={{
Comment on lines +25 to +27

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

행님 이곳에 isNew가 누락된 것 같습니다. isNew를 심폐소생술로 부활시켜야할 것 같다데스⚔️

tagList: card.tagList,
title: card.title ?? '',
content: card.content ?? '',
fileCount: card.fileCount ?? 0,
imageCount: card.imageCount ?? 0,
createAt: card.createdAt ?? '',
}}
Comment on lines +27 to +34

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props로 보내는게 많아져서 card 객체를 통째로 보내고 Card 컴포넌트 내부에서 구조 분해 할당으로 데이터를 추출하면 좋을 것 같아요 👍

isSelected={isSelected}
isDragging={isDragging}
onClick={onClickCard}
/>
))}
</div>
</div>
);
};

export default MemoList;
47 changes: 47 additions & 0 deletions apps/client/src/pages/memo/hooks/use-infinite-scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useEffect, useRef } from 'react';
import { type FetchNextPageOptions } from '@tanstack/react-query';

// 바닥에 닿기 전에 미리 불러와 로딩을 기다리는 구간을 없앤다
const LOAD_MORE_ROOT_MARGIN = '200px';

interface UseInfiniteScrollProps {
hasNextPage: boolean;
isFetchingNextPage: boolean;
fetchNextPage: (options?: FetchNextPageOptions) => void;
}

/**
* 목록 끝에 둔 요소가 화면에 들어오면 다음 페이지를 불러오는 무한 스크롤 훅
*
*
* @param hasNextPage - false면 관측하지 않는다
* @param isFetchingNextPage - 패칭 중에는 관측을 멈춘다. 패칭이 끝나면 옵저버를
* 다시 만들어 관측 요소가 계속 보이는 상태에서도 다음 페이지가 이어진다
* @param fetchNextPage - cancelRefetch 기본값(true)은 진행 중인 요청을 취소하고
* 재시작하므로 false로 넘겨 호출한다
* @returns 목록 마지막에 배치할 요소의 ref
*/
export const useInfiniteScroll = ({
hasNextPage,
isFetchingNextPage,
fetchNextPage,
}: UseInfiniteScrollProps) => {
const loadMoreRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const loadMoreTarget = loadMoreRef.current;
if (!loadMoreTarget || !hasNextPage || isFetchingNextPage) return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4) 인지 관점에서 아래 코드처럼 하는 것도 깔끔해져요!

  if (!loadMoreTarget) return;
  if (!hasNextPage) return;
  if (isFetchingNextPage) return;


const observer = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) fetchNextPage({ cancelRefetch: false });
},
{ rootMargin: LOAD_MORE_ROOT_MARGIN },
);
observer.observe(loadMoreTarget);

return () => observer.disconnect();
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);

return loadMoreRef;
};
10 changes: 4 additions & 6 deletions apps/client/src/pages/memo/memo-page.css.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { style } from '@vanilla-extract/css';

import { themeVars } from '@cds/ui';

export const container = style({
...themeVars.fontStyles.display_sb_36,
backgroundImage: themeVars.color.gradient02,
color: themeVars.color.grey700,
width: '30rem',
width: '100%',
paddingInline: '4rem',
display: 'flex',
justifyContent: 'center',
});
65 changes: 42 additions & 23 deletions apps/client/src/pages/memo/memo-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,62 @@ import { PATH } from '@router/path';
import { useNavigate, useSearchParams } from 'react-router';

import EmptyView from '@shared/components/empty-view/empty-view';
import MemoListView from '@shared/components/memo-list-view/memo-list-view';

import { useGetAllMemo, useGetMemoTotalCount } from './apis/queries';
import MemoList from './components/memo-list/memo-list';
import { useInfiniteScroll } from './hooks/use-infinite-scroll';

import * as styles from './memo-page.css';

import emptyImage from '/empty.svg';

const AllMemoPage = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const tagParam = searchParams.get('tag');
const tagId = tagParam !== null ? [Number(tagParam)] : undefined;
const tagIds = tagParam !== null ? [Number(tagParam)] : undefined;

const {
data: filteredMemos,
data: memosList,
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = useGetAllMemo(tagId);
const { data: totalCount } = useGetMemoTotalCount(tagId);

return totalCount === 0 ? (
<EmptyView
imgSrc={emptyImage}
title="작성된 메모가 없습니다."
description="새 메모 창에 들어가서 새로운 메모를 생성해보세요."
buttonText="메모 작성하러 가기"
onButtonClick={() => navigate(PATH.NEW_MEMO)}
/>
) : (
<MemoListView
title="전체 메모"
initialMemos={filteredMemos}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
totalCount={totalCount ?? 0}
/>
} = useGetAllMemo(tagIds);
const { data: totalCount } = useGetMemoTotalCount(tagIds);

const loadMoreRef = useInfiniteScroll({
hasNextPage,
isFetchingNextPage,
fetchNextPage,
});

return (
<>
{/* MemoHeader 컴포넌트 */}

<div className={styles.container}>
{totalCount === 0 ? (
<EmptyView
imgSrc={emptyImage}
title="작성된 메모가 없습니다."
description="새 메모 창에 들어가서 새로운 메모를 생성해보세요."
buttonText="메모 작성하러 가기"
onButtonClick={() => navigate(PATH.NEW_MEMO)}
/>
) : (
<>
{/* 카드 선택/드래그, 상세 페이지 연결 전까지 임시 값 전달 */}
<MemoList
cards={memosList ?? []}
isSelected={false}
isDragging={false}
onClickCard={() => {}}
/>
<div ref={loadMoreRef} />
Comment on lines +49 to +56

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

임시로 이렇게 둘께욥 !

</>
)}
</div>
</>
);
};

Expand Down
11 changes: 6 additions & 5 deletions apps/client/src/shared/components/card/card.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const baseStyle = {
padding: '2.4rem',
width: '38rem',
minWidth: '34rem',
maxWidth: '100%',
height: '22rem',
borderRadius: '12px',
backgroundColor: themeVars.color.white,
Expand All @@ -34,7 +35,7 @@ const draggingStyle = {
cursor: 'grabbing',
} as const;

const isNewAiSelectedStyle = {
const isNewSelectedStyle = {
border: `1px solid ${themeVars.color.grey400}`,
background: themeVars.color.grey100,
selectors: {
Expand All @@ -45,7 +46,7 @@ const isNewAiSelectedStyle = {
},
} as const;

const isNewAiStyle = {
const isNewStyle = {
border: '1px solid transparent',
background: `
${themeVars.color.gradient03} padding-box,
Expand All @@ -65,12 +66,12 @@ export const cardContainer = recipe({
variants: {
isSelected: { true: selectedStyle, false: {} },
isDragging: { true: draggingStyle, false: {} },
isNewAi: { true: isNewAiStyle, false: {} },
isNew: { true: isNewStyle, false: {} },
},
compoundVariants: [
{
variants: { isNewAi: true, isSelected: true },
style: isNewAiSelectedStyle,
variants: { isNew: true, isSelected: true },
style: isNewSelectedStyle,
},
],
});
Expand Down
26 changes: 14 additions & 12 deletions apps/client/src/shared/components/card/card.tsx

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

card.tsxmemo-list.tsx에서만 쓰인다면 위치를 옮기는게 좋아보이네용

Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,45 @@ import { ComponentProps } from 'react';
import { Icon } from '@cds/icon';
import { Label, Title } from '@cds/ui';

import { components } from '@shared/types/schema';
import { formatDate } from '@shared/utils/format-date';

import * as styles from './card.css';

// TODO: Tag 백엔드 타입에 맞게 변경
type TagType = {
tagId?: number;
name?: string;
};

type CardInfoType = {
tagList?: TagType[];
tagList?: components['schemas']['TagResponse'][];
title: string;
content: string;
fileCount: number;
imageCount: number;
createAt: string;
isNew?: boolean;
};
interface CardProps extends ComponentProps<'article'> {
card: CardInfoType;
isSelected?: boolean;
isDragging?: boolean;
isNewAi?: boolean;
}

const Card = ({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Card 컴포넌트의 이름 더 구체화하면 어떨까요?
UI 중에 card라는 명칭을 가진 UI가 있어서 의미를 더 드러내면 좋을 것 같아요

https://namethatui.com/web/card

card: { tagList = [], title, content, fileCount, imageCount, createAt },
card: {
tagList = [],
title,
content,
fileCount,
imageCount,
createAt,
isNew = false,
},
isSelected = false,
isDragging = false,
isNewAi = false,
...props
}: CardProps) => {
return (
<article
className={styles.cardContainer({ isSelected, isDragging, isNewAi })}
draggable
{...props}
className={styles.cardContainer({ isSelected, isDragging, isNew })}
draggable
>
<div className={styles.mainInfoContainer}>
<div className={styles.tagContainer}>
Expand Down

This file was deleted.

Loading
Loading