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

[Feat] 결과 수정 페이지 퍼블리싱 및 DND 모듈 구현 #108

Merged
merged 11 commits into from
Feb 6, 2025
5 changes: 4 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
"check-types": "tsc --noEmit"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@repo/theme": "workspace:^",
"@repo/ui": "workspace:^",
"@tanstack/react-query": "^5.66.0",
"@vanilla-extract/css": "^1.17.0",
"cookies-next": "^5.1.0",
"ky": "^1.7.4",
"emoji-picker-react": "^4.12.0",
"ky": "^1.7.4",
"motion": "^11.17.0",
"next": "14.2.21",
"overlay-kit": "^1.4.1",
Expand Down
201 changes: 201 additions & 0 deletions apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
'use client';

import React, { RefObject } from 'react';
import { useScroll } from '@web/hooks';
import * as style from './pageStyle.css';
import { NavBar, MainBreadcrumbItem } from '@web/components/common';
import { Breadcrumb, Button, Chip, Icon, Accordion } from '@repo/ui';
import { POST_STATUS } from '@web/types/post';
import { INITIAL_CONTENT_ITEMS } from './constants';
import {
DndControler,
useDndControler,
} from '@web/components/common/DND/DndControler';
import { EditPageParams } from './types';
import { DragGuide } from './_components/DragGuide/DragGuide';

type EditContentProps = {
scrollRef: RefObject<HTMLDivElement>;
isScrolled: boolean;
agentId: EditPageParams['agentId'];
postGroupId: EditPageParams['postGroupId'];
};

function EditContent({
scrollRef,
isScrolled,
agentId,
postGroupId,
}: EditContentProps) {
const { getItemsByStatus, handleRemove } = useDndControler();

return (
<div className={style.mainStyle} ref={scrollRef}>
<NavBar
leftAddon={
<Breadcrumb>
<MainBreadcrumbItem href="/" />
<Breadcrumb.Item active>기초 경제 지식</Breadcrumb.Item>
</Breadcrumb>
}
rightAddon={
<Button
type="submit"
size="large"
variant="primary"
leftAddon={<Icon name="checkCalendar" size={20} />}
onClick={() => {}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

빈 이벤트 핸들러 구현이 필요합니다.

onClickonModify 핸들러가 비어있습니다. 이는 사용자 상호작용에 대한 피드백이 없음을 의미합니다.

각 핸들러에 대한 구체적인 구현이 필요합니다. 임시로 TODO 주석이라도 추가하는 것을 권장합니다.

Also applies to: 89-89, 120-120, 155-155

disabled={false}
isLoading={false}
className={style.submitButtonStyle}
>
예약하러 가기
</Button>
}
isScrolled={isScrolled}
/>
<div className={style.contentStyle}>
<Accordion
type="multiple"
defaultValue={[
POST_STATUS.GENERATED,
POST_STATUS.EDITING,
POST_STATUS.READY_TO_UPLOAD,
]}
className={style.accordionStyle}
>
{/* 생성된 글 영역 */}
<Accordion.Item
value={POST_STATUS.GENERATED}
className={style.accordionItemStyle}
>
<Accordion.Trigger className={style.accordionTriggerStyle}>
<Chip variant="grey">생성된 글</Chip>
</Accordion.Trigger>
<Accordion.Content>
<DndControler.Droppable id={POST_STATUS.GENERATED}>
<DndControler.SortableList
items={getItemsByStatus(POST_STATUS.GENERATED).map(
(item) => item.id
)}
>
{getItemsByStatus(POST_STATUS.GENERATED).map((item) => (
<DndControler.Item
key={item.id}
id={item.id}
summary={item.summary}
updatedAt={item.updatedAt}
onRemove={() => handleRemove(item.id)}
onModify={() => {}}
/>
))}
</DndControler.SortableList>
</DndControler.Droppable>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

접근성 개선이 필요합니다.

드래그 앤 드롭 영역에 대한 접근성 속성이 누락되어 있습니다.

다음 접근성 속성들을 추가하는 것을 권장합니다:

  • aria-label
  • role="region"
  • 키보드 네비게이션 지원

예시:

<DndControler.Droppable id={POST_STATUS.GENERATED}>
+ <div
+   role="region"
+   aria-label="생성된 글 목록"
+ >
    <DndControler.SortableList
+     role="list"
      items={getItemsByStatus(POST_STATUS.GENERATED).map(
        (item) => item.id
      )}
    >
      {/* ... */}
    </DndControler.SortableList>
+ </div>
</DndControler.Droppable>

Also applies to: 106-127, 140-163

</Accordion.Content>
</Accordion.Item>

{/* 수정 중인 글 영역 */}
<Accordion.Item
value={POST_STATUS.EDITING}
className={style.accordionItemStyle}
>
<Accordion.Trigger className={style.accordionTriggerStyle}>
<Chip variant="purple">수정 중인 글</Chip>
</Accordion.Trigger>
<Accordion.Content id={POST_STATUS.EDITING}>
<DndControler.Droppable id={POST_STATUS.EDITING}>
<DndControler.SortableList
items={getItemsByStatus(POST_STATUS.EDITING).map(
(item) => item.id
)}
>
{getItemsByStatus(POST_STATUS.EDITING).length > 0 ? (
getItemsByStatus(POST_STATUS.EDITING).map((item) => (
<DndControler.Item
key={item.id}
id={item.id}
summary={item.summary}
updatedAt={item.updatedAt}
onRemove={() => handleRemove(item.id)}
onModify={() => {}}
/>
))
) : (
<DragGuide description="수정 중인 글을 끌어서 여기에 놓아주세요" />
)}
</DndControler.SortableList>
</DndControler.Droppable>
</Accordion.Content>
</Accordion.Item>

{/* 업로드할 글 영역 */}
<Accordion.Item
value={POST_STATUS.READY_TO_UPLOAD}
className={style.accordionItemStyle}
>
<Accordion.Trigger className={style.accordionTriggerStyle}>
<Chip variant="green">업로드할 글</Chip>
</Accordion.Trigger>
<Accordion.Content id={POST_STATUS.READY_TO_UPLOAD}>
<DndControler.Droppable id={POST_STATUS.READY_TO_UPLOAD}>
<DndControler.SortableList
items={getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).map(
(item) => item.id
)}
>
{getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).length > 0 ? (
getItemsByStatus(POST_STATUS.READY_TO_UPLOAD).map(
(item) => (
<DndControler.Item
key={item.id}
id={item.id}
summary={item.summary}
updatedAt={item.updatedAt}
onRemove={() => handleRemove(item.id)}
onModify={() => {}}
/>
)
)
) : (
<DragGuide description="업로드가 준비된 글을 끌어서 여기에 놓아주세요" />
)}
</DndControler.SortableList>
</DndControler.Droppable>
</Accordion.Content>
</Accordion.Item>
</Accordion>
</div>
</div>
);
}

export default function Edit({ agentId, postGroupId }: EditPageParams) {
const [scrollRef, isScrolled] = useScroll<HTMLDivElement>({ threshold: 100 });

return (
<DndControler
initialItems={INITIAL_CONTENT_ITEMS}
onDragEnd={(items) => {
console.log('=== Current Items Status ===');
const itemsByStatus = {
GENERATED: items.filter((item) => item.status === 'GENERATED'),
EDITING: items.filter((item) => item.status === 'EDITING'),
READY_TO_UPLOAD: items.filter(
(item) => item.status === 'READY_TO_UPLOAD'
),
};
console.log('GENERATED:', itemsByStatus.GENERATED);
console.log('EDITING:', itemsByStatus.EDITING);
console.log('READY_TO_UPLOAD:', itemsByStatus.READY_TO_UPLOAD);
console.log('========================');
}}
Comment on lines +175 to +188
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 코드에서 console.log 제거가 필요합니다.

디버깅을 위한 console.log 문은 프로덕션 환경에서 제거되어야 합니다.

다음과 같이 수정하는 것을 권장합니다:

onDragEnd={(items) => {
-  console.log('=== Current Items Status ===');
-  const itemsByStatus = {
-    GENERATED: items.filter((item) => item.status === 'GENERATED'),
-    EDITING: items.filter((item) => item.status === 'EDITING'),
-    READY_TO_UPLOAD: items.filter(
-      (item) => item.status === 'READY_TO_UPLOAD'
-    ),
-  };
-  console.log('GENERATED:', itemsByStatus.GENERATED);
-  console.log('EDITING:', itemsByStatus.EDITING);
-  console.log('READY_TO_UPLOAD:', itemsByStatus.READY_TO_UPLOAD);
-  console.log('========================');
+  // TODO: 상태 변경에 따른 처리 로직 구현
}}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onDragEnd={(items) => {
console.log('=== Current Items Status ===');
const itemsByStatus = {
GENERATED: items.filter((item) => item.status === 'GENERATED'),
EDITING: items.filter((item) => item.status === 'EDITING'),
READY_TO_UPLOAD: items.filter(
(item) => item.status === 'READY_TO_UPLOAD'
),
};
console.log('GENERATED:', itemsByStatus.GENERATED);
console.log('EDITING:', itemsByStatus.EDITING);
console.log('READY_TO_UPLOAD:', itemsByStatus.READY_TO_UPLOAD);
console.log('========================');
}}
onDragEnd={(items) => {
// TODO: 상태 변경에 따른 처리 로직 구현
}}

>
<EditContent
scrollRef={scrollRef}
isScrolled={isScrolled}
agentId={agentId}
postGroupId={postGroupId}
/>
</DndControler>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { style } from '@vanilla-extract/css';
import { vars } from '@repo/theme';

export const container = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%',
height: 'fit-content',
borderRadius: vars.borderRadius[24],
border: `1px solid ${vars.colors.grey50}`,
background: vars.colors.grey,
padding: vars.space[40],
gap: vars.space[32],
alignSelf: 'stretch',
});

export const description = style({
textAlign: 'center',
whiteSpace: 'pre-wrap',
});

export const image = style({
width: '100%',
height: '100%',
maxWidth: '42rem',
maxHeight: '12.8rem',
});
Copy link
Member

Choose a reason for hiding this comment

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

와 이 부분 전체를 SVG로 넣으시려나 했는데 직접 구현하셨군요 👍🏻 👍🏻

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Image from 'next/image';
import * as styles from './DragGuide.css';
import { Text } from '@repo/ui';
import DNDImage from '@web/assets/images/dndImage.webp';

type DragGuideProps = {
description: string;
};

export function DragGuide({ description }: DragGuideProps) {
return (
<div className={styles.container}>
<Text.H2
className={styles.description}
fontSize={20}
fontWeight="semibold"
color="grey500"
>
{description}
</Text.H2>
<Image
src={DNDImage}
alt="드래그 앤 드롭 이미지"
className={styles.image}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Post } from '@web/types';
import { POST_STATUS } from '@web/types/post';

//MEMO: 임시 더미
export const INITIAL_CONTENT_ITEMS: Post[] = [
{
id: 1,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
summary: '경제 기초 지식 1에 대한 요약',
Comment on lines +8 to +10
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타임스탬프 생성 방식의 개선이 필요합니다.

상수에서 new Date()를 직접 사용하면 매번 다른 값이 생성되어 일관성 문제가 발생할 수 있습니다.

다음과 같이 수정하는 것을 권장합니다:

+ const BASE_DATE = '2024-02-20T00:00:00Z';

{
  id: 1,
-  createdAt: new Date().toISOString(),
-  updatedAt: new Date().toISOString(),
+  createdAt: BASE_DATE,
+  updatedAt: BASE_DATE,
  // ... 다른 속성들
}

Also applies to: 24-25, 34-35, 44-45, 54-55

content: '경제 기초 지식 1의 상세 내용',
postImages: [
{
id: 1,
postId: 1,
url: 'https://example.com/image1.jpg',
},
],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
},
{
id: 2,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
summary: '경제 기초 지식 2에 대한 요약',
content: '경제 기초 지식 2의 상세 내용',
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
},
{
id: 3,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
summary: '경제 기초 지식 3에 대한 요약',
content: '경제 기초 지식 3의 상세 내용',
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
},
{
id: 4,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
summary: '경제 기초 지식 4에 대한 요약',
content: '경제 기초 지식 4의 상세 내용',
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
},
{
id: 5,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
summary: '경제 기초 지식 5에 대한 요약',
content: '경제 기초 지식 5의 상세 내용',
postImages: [],
status: POST_STATUS.GENERATED,
uploadTime: new Date().toISOString(),
},
];
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getTimeAgo } from '../../_utils';
import {
contentItemStyle,
cursorGrabStyle,
Expand All @@ -11,6 +10,7 @@ import {
import { Icon } from '@repo/ui/Icon';
import { IconButton } from '@repo/ui/IconButton';
import { Text } from '@repo/ui/Text';
import { getTimeAgo } from '@web/utils';

export type ContentItemProps = {
image?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EditPost } from '../_components/EditPost/EditPost';
import { EditSidebar } from '../_components/EditSidebar/EditSidebar';
import { EditPost } from './_components/EditPost/EditPost';
import { EditSidebar } from './_components/EditSidebar/EditSidebar';
import { editDetailPage, flexColumn } from './page.css';

export default function EditDetailPage() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Edit from './Edit';
import type { EditPageProps } from './types';

export default function EditPage({ params }: EditPageProps) {
return <Edit agentId={params.agentId} postGroupId={params.postGroupId} />;
}
Loading
Loading