-
Notifications
You must be signed in to change notification settings - Fork 0
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 관련 모듈 수정 #117
Conversation
…m-1-FE into feat/#a056e3fb9-subscribe-page
Walkthrough이번 풀 리퀘스트에서는 업로드 예약 페이지 퍼블리싱 작업과 드래그 앤 드롭(DnD) 모듈 전반의 수정이 이루어졌습니다. 기존의 인라인 DND 아이템 렌더링을 ContentItem 등 재사용 가능한 컴포넌트들을 이용하도록 개선하였고, drag overlay 렌더링 방식을 동적 처리하도록 업데이트하였습니다. 또한, 예약 기능 관련 컴포넌트(Schedule, ScheduleTable, SideBar, TableRow, TitleWithDescription 등)와 관련 스타일, 타입 정의가 추가되었으며, NavBar와 DnD 모듈 내 일부 컴포넌트 및 로직도 명칭 변경 및 리팩토링되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant SP as SchedulePage
participant S as Schedule
participant TC as DndController
U->>SP: 페이지 접근 및 예약 데이터 요청
SP->>S: 서버 사이드 토큰 및 데이터 페치
S->>TC: 드래그 시작 및 항목 이동 처리
TC->>S: 업데이트된 순서 반환
U->>S: 포스트 수정/삭제 요청
S->>SP: UI 갱신 후 결과 반영
Assessment against linked issues
Possibly related PRs
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (3)
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx (1)
73-83
: 에러 처리 로직 추가가 필요합니다.
onSubmit
함수에 에러 처리 로직이 누락되어 있습니다. API 호출 실패 시 사용자에게 적절한 피드백을 제공하고, 입력된 프롬프트 값을 유지하는 것이 좋습니다.const onSubmit = (data: PromptForm) => { const editingPostIds = getItemsByStatus(POST_STATUS.EDITING).map( (item) => item.id ); updatePrompt({ prompt: data.prompt, postsId: editingPostIds, + onError: (error) => { + modal.alert({ + title: '업데이트 실패', + description: '프롬프트 업데이트 중 오류가 발생했습니다.', + icon: <Modal.Icon name="notice" color="warning500" />, + }); + }, + onSuccess: () => { + setValue('prompt', ''); + }, }); - setValue('prompt', ''); };apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/detail/_components/EditSidebar/EditSidebar.tsx (1)
215-215
: 드래그 가이드 메시지가 불완전합니다."수정하고 싶은 글을"이라는 메시지가 불완전해 보입니다. 다른 DragGuide 컴포넌트들처럼 완전한 문장으로 수정이 필요합니다.
-<DragGuide description="수정하고 싶은 글을" /> +<DragGuide description="수정하고 싶은 글을 끌어서 여기에 놓아주세요" />apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/ContentItem/ContentItem.tsx (1)
71-73
: 이미지 플레이스홀더를 개선하세요.현재 이미지 플레이스홀더가 의미 없는 'x' 문자를 사용하고 있습니다.
- {image ? ( - <div>x</div> + {image ? ( + <img + src={image} + alt="콘텐츠 썸네일" + className={noShrinkStyle} + width={32} + height={32} + /> ) : (
🧹 Nitpick comments (20)
apps/web/src/components/common/NavBar/NavBar.tsx (1)
23-24
: addon 래퍼 구현이 개선되었습니다만, null 처리가 필요합니다.addon 요소들을 스타일된 div로 래핑하여 구조가 개선되었습니다. 하지만 leftAddon이나 rightAddon이 없을 경우에도 빈 div가 렌더링됩니다.
다음과 같이 조건부 렌더링을 적용하는 것을 고려해보세요:
- <div className={styles.addonStyle}>{leftAddon}</div> - <div className={styles.addonStyle}>{rightAddon}</div> + {leftAddon && <div className={styles.addonStyle}>{leftAddon}</div>} + {rightAddon && <div className={styles.addonStyle}>{rightAddon}</div>}apps/web/src/components/common/DNDController/context/DndContext.tsx (2)
59-61
: activeItem 로직이 개선되었습니다.드래그 중인 아이템을 찾는 로직이 명확하게 구현되었습니다. 하지만 성능 최적화의 여지가 있습니다.
useMemo
를 사용하여activeItem
계산을 최적화하는 것을 고려해보세요:- const activeItem = dnd.activeId - ? dnd.items.find((item) => item.id === dnd.activeId) - : null; + const activeItem = useMemo(() => { + return dnd.activeId + ? dnd.items.find((item) => item.id === dnd.activeId) + : null; + }, [dnd.activeId, dnd.items]);
90-90
: 드래그 오버레이 렌더링 로직이 개선되었습니다.조건부 렌더링을 통해 커스텀 오버레이 구현이 가능해졌습니다. 하지만 fallback UI가 없는 경우를 고려해야 합니다.
기본 오버레이 컴포넌트를 제공하는 것을 고려해보세요:
- {activeItem && renderDragOverlay && renderDragOverlay(activeItem)} + {activeItem && (renderDragOverlay + ? renderDragOverlay(activeItem) + : <DefaultDragOverlay item={activeItem} /> + )}apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts (2)
153-178
: 동일 상태 내 재정렬 로직 최적화 제안현재 구현은 정상적으로 작동하지만, 다음과 같은 개선사항을 고려해보시면 좋을 것 같습니다:
- 불필요한 상태 업데이트를 방지하기 위해
oldIndex
와newIndex
가 동일한 경우를 체크- 재사용 가능한 정렬 로직을 별도 함수로 추출
다음과 같이 코드를 개선해보세요:
setItems((prev) => { const itemsByStatus = createItemsByStatus(prev); const statusItems = itemsByStatus[draggedItem.status]; const oldIndex = statusItems.findIndex( (item) => item.id === draggedItemId ); const newIndex = statusItems.findIndex((item) => item.id === overId); + if (oldIndex === newIndex) return prev; const reorderedItems = arrayMove(statusItems, oldIndex, newIndex); reorderedItems.forEach((item, index) => { item.displayOrder = index + 1; }); itemsByStatus[draggedItem.status] = reorderedItems; const newItems = updateDisplayOrders( Object.values(itemsByStatus).flat() ); onDragEnd?.(newItems); return newItems; });
44-46
: 타입 안전성 개선 제안드래그된 아이템을 찾는 로직에 타입 가드를 추가하면 런타임 안전성이 향상될 것 같습니다.
다음과 같이 타입 가드를 추가해보세요:
-const draggedItemId = Number(active.id); -const draggedItem = items.find((item) => item.id === draggedItemId); -if (!draggedItem) return; +const draggedItemId = Number(active.id); +if (isNaN(draggedItemId)) return; +const draggedItem = items.find((item) => item.id === draggedItemId); +if (!draggedItem || typeof draggedItem.status !== 'string') return;Also applies to: 118-120
apps/web/src/components/common/DNDController/compounds/DraggableItem/DraggableItem.tsx (1)
9-45
: 컴포넌트 구현 개선사항 검토컴포넌트가 더 범용적이고 재사용 가능하도록 개선되었습니다:
- children prop을 통해 유연한 콘텐츠 렌더링이 가능해졌습니다
- 기본 스타일링과 이벤트 처리가 잘 구현되어 있습니다
하나의 개선 제안이 있습니다:
className에 대한 타입 안전성을 높이기 위해 CSS 모듈이나 styled-components 사용을 고려해보세요.
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx (2)
77-82
: 드래그 오버레이 구현 검토드래그 중인 아이템에 대한 시각적 피드백이 잘 구현되었습니다. 다만 몇 가지 개선사항을 제안드립니다:
- 성능 최적화를 위해 renderDragOverlay를 useCallback으로 메모이제이션하는 것을 고려해보세요.
- activeItem이 undefined일 경우에 대한 처리가 필요할 수 있습니다.
예시 코드:
+ const renderDragOverlay = useCallback((activeItem) => { + if (!activeItem) return null; return ( <ContentItem summary={activeItem.summary} updatedAt={activeItem.updatedAt} /> ); + }, []);
65-65
: 키 생성 로직 검토posts 배열의 id를 이용한 key 생성이 적절히 구현되었습니다. 다만, 성능상의 이점을 위해 다음과 같은 개선을 고려해보세요:
- key={posts.data.posts.map((p) => p.id).join(',')} + key={posts.data.posts.map((p) => p.id).join('_')}문자열 연결 시 쉼표 대신 언더스코어를 사용하면 URL 인코딩이 필요한 경우에 더 안전할 수 있습니다.
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/SideBar/SideBar.tsx (1)
10-12
: 접근성 개선을 위한 제안사이드바의 의미를 스크린 리더에 전달하기 위해 ARIA 속성을 추가하는 것이 좋습니다.
- return <div className={style.sidebarStyle}>{children}</div>; + return <div className={style.sidebarStyle} role="complementary" aria-label="사이드바">{children}</div>;apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/SideBar/SideBar.css.ts (1)
4-14
: 반응형 디자인 개선 제안현재 구현은 고정된 너비와 절대 위치를 사용하고 있어 다양한 화면 크기에서 문제가 발생할 수 있습니다.
export const sidebarStyle = style({ - width: '44rem', + width: 'min(44rem, 100%)', height: '100%', backgroundColor: 'transparent', padding: `0 ${vars.space[40]}`, - position: 'absolute', + position: 'relative', top: 0, left: 0, borderRight: `1px solid ${vars.colors.grey200}`, paddingTop: '8rem', });apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/types.ts (2)
10-14
: 타입 안정성 개선 제안
onDragEnd
콜백 함수의 에러 처리를 위한 타입 개선이 필요합니다.export type ScheduleTableProps = { columns: Column[]; items: Post[]; - onDragEnd?: (items: Post[]) => void; + onDragEnd?: (items: Post[]) => Promise<void> | void; };
4-8
: Column 타입 개선 제안width 속성의 타입을 더 구체적으로 정의하면 좋을 것 같습니다.
export type Column = { id: string; label: string; - width: string; + width: `${number}${'px' | 'rem' | '%'}`; };apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.css.ts (1)
4-10
: 테이블 행 스타일이 잘 정의되어 있습니다.
cursor: grab
스타일은 드래그 앤 드롭 기능을 직관적으로 나타내며, 플렉스 레이아웃과 테두리 스타일이 적절하게 적용되어 있습니다.드래그 중일 때의 커서 스타일도 고려해보세요:
export const tableRowStyle = style({ display: 'flex', alignItems: 'center', width: '100%', cursor: 'grab', + ':active': { + cursor: 'grabbing', + }, borderBottom: `1px solid ${vars.colors.grey200}`, });apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TitleWithDescription/TitleWithDescription.tsx (1)
4-23
: 제목과 설명을 위한 컴포넌트가 깔끔하게 구현되어 있습니다.Text 컴포넌트를 활용한 타이포그래피 스타일링이 일관성 있게 적용되어 있습니다.
접근성 향상을 위해 시맨틱 마크업을 고려해보세요:
export function TitleWithDescription({ title, description, }: TitleWithDescriptionProps) { return ( - <div className={style.textWrapperStyle}> + <section className={style.textWrapperStyle} aria-label={title}> <Text.H2 fontSize={28} fontWeight="bold" color="grey1000"> {title} </Text.H2> <Text.P fontSize={18} fontWeight="medium" color="grey500"> {description} </Text.P> - </div> + </section> ); }apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/ScheduleTable.css.ts (1)
12-20
: 테이블 스타일이 체계적으로 구성되어 있습니다.테이블 레이아웃과 여백 처리가 잘 되어 있으나, 테두리 처리를 개선할 수 있습니다.
테이블 테두리 스타일을 더 일관성 있게 적용해보세요:
export const table = style({ width: '100%', borderCollapse: 'separate', borderSpacing: 0, backgroundColor: 'transparent', borderRadius: vars.borderRadius[8], + border: `1px solid ${vars.colors.grey200}`, overflow: 'hidden', tableLayout: 'fixed', });
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.tsx (1)
20-20
: 하드코딩된 텍스트를 상수로 분리하세요."드롭다운" 텍스트가 여러 번 반복되어 있습니다. 이를 상수로 분리하여 관리하면 유지보수가 용이해집니다.
+const DROPDOWN_TEXT = '드롭다운'; export function TableRow({ columns, ...contentItemProps }: TableRowProps) { return ( <div className={style.tableRowStyle}> - <div className={style.tableRowCellStyle}> - 드롭다운 + <div className={style.tableRowCellStyle}> + {DROPDOWN_TEXT} </div> // ... 다른 셀들도 동일하게 수정Also applies to: 26-26, 32-32
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/pageStyle.css.ts (2)
6-6
: 하드코딩된 값을 테마 변수로 이동하세요.
144rem
과 같은 하드코딩된 값은 테마 시스템을 통해 관리되어야 합니다.+// theme.ts에 추가 +export const sizes = { + sidebar: '44rem', + content: '100rem', + mainWidth: 'calc(var(--content) + var(--sidebar))', +} as const; export const mainStyle = style({ position: 'relative', - minWidth: '144rem', // 100rem + 44rem + minWidth: vars.sizes.mainWidth, minHeight: '100vh', // ... });
44-54
: 유사한 스타일 패턴을 재사용 가능한 믹스인으로 분리하세요.
sideBarContentWrapperStyle
와dropdownWrapperStyle
이 유사한 flexbox 패턴을 사용하고 있습니다.+const flexColumnMixin = style({ + display: 'flex', + flexDirection: 'column', +}); export const sideBarContentWrapperStyle = style([ + flexColumnMixin, { - display: 'flex', - flexDirection: 'column', gap: vars.space[40], }, ]); export const dropdownWrapperStyle = style([ + flexColumnMixin, { - display: 'flex', - flexDirection: 'column', gap: vars.space[16], }, ]);apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/ScheduleTable.tsx (1)
68-79
: 데이터 매핑 최적화가 필요합니다.
data.map
이 두 번 호출되어 불필요한 반복이 발생하고 있습니다.- <DndController.SortableList items={data.map((item) => item.id)}> - {data.map((item) => ( + <DndController.SortableList + items={data.map((item) => ({ + id: item.id, + element: ( <DndController.Item id={item.id} key={item.id}> <TableRow columns={columns} onModify={() => handleModify(item.id)} onRemove={() => handleDeletePost(item.id)} {...item} /> </DndController.Item> + ), + }))} + > + {(item) => item.element} - ))} </DndController.SortableList>apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/Schedule.tsx (1)
29-50
: 칼럼 설정을 상수 파일로 분리 권장칼럼 설정이 컴포넌트 내부에 하드코딩되어 있어 유지보수가 어려울 수 있습니다.
constants.ts
파일을 생성하여 다음과 같이 분리하는 것을 제안드립니다:// constants.ts export const SCHEDULE_TABLE_COLUMNS: Column[] = [ { id: 'date', label: '날짜 변경', width: '16.6rem', }, // ... 나머지 칼럼 설정 ]; // Schedule.tsx에서는 다음과 같이 사용 import { SCHEDULE_TABLE_COLUMNS } from './constants';
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/Edit.tsx
(3 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/ContentItem/ContentItem.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/ContentItem/ContentItem.tsx
(3 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx
(4 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/detail/_components/EditSidebar/EditSidebar.tsx
(5 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts
(0 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/Schedule.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/ScheduleTable.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/ScheduleTable.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/types.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/SideBar/SideBar.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/SideBar/SideBar.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TitleWithDescription/TitleWithDescription.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TitleWithDescription/TitleWithDescription.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/page.tsx
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/pageStyle.css.ts
(1 hunks)apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/type.ts
(1 hunks)apps/web/src/components/common/DNDController/DndController.tsx
(2 hunks)apps/web/src/components/common/DNDController/compounds/DraggableItem/DraggableItem.tsx
(2 hunks)apps/web/src/components/common/DNDController/compounds/index.ts
(1 hunks)apps/web/src/components/common/DNDController/context/DndContext.tsx
(5 hunks)apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts
(3 hunks)apps/web/src/components/common/NavBar/NavBar.css.ts
(1 hunks)apps/web/src/components/common/NavBar/NavBar.tsx
(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/pageStyle.css.ts
✅ Files skipped from review due to trivial changes (1)
- apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/type.ts
🔇 Additional comments (20)
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/ContentItem/ContentItem.css.ts (2)
11-11
: 드래그 가능한 요소임을 시각적으로 표시하는 커서 스타일이 추가되었습니다.사용자 경험 향상을 위해 드래그 가능한 요소임을 직관적으로 알 수 있도록 'grab' 커서가 적용되었습니다.
3-46
: 스타일 구조가 잘 정리되어 있습니다.각 스타일이 명확한 목적을 가지고 분리되어 있으며, 선택자를 활용한 호버 효과도 깔끔하게 구현되어 있습니다. 특히:
- contentItemStyle: 기본 레이아웃과 드래그 인터랙션
- summaryStyle: 텍스트 오버플로우 처리
- timeStyle & iconHoverStyle: 호버 시 동작하는 UI 전환
apps/web/src/components/common/NavBar/NavBar.css.ts (1)
31-34
: 새로운 addon 스타일이 적절하게 구현되었습니다.flex 컨테이너를 사용하여 addon 요소들을 중앙 정렬하는 스타일이 깔끔하게 구현되었습니다.
apps/web/src/components/common/DNDController/context/DndContext.tsx (3)
10-10
: 충돌 감지 전략이 변경되었습니다.
closestCenter
에서closestCorners
로 변경되었습니다. 이는 드래그 앤 드롭 시 아이템 위치 결정 방식에 영향을 미칩니다.드래그 앤 드롭 동작이 의도한 대로 작동하는지 확인해 주세요. 특히 아이템이 많은 경우나 아이템 크기가 다양한 경우에 대해 테스트가 필요합니다.
23-23
: 커스텀 드래그 오버레이 렌더링 기능이 추가되었습니다.
renderDragOverlay
옵션 prop이 추가되어 드래그 중인 아이템의 시각적 표현을 커스터마이즈할 수 있게 되었습니다.
82-84
: 드롭 영역 측정 전략이 적절합니다.
MeasuringStrategy.Always
를 사용하여 드롭 영역을 지속적으로 측정하는 것은 정확한 드래그 앤 드롭을 위해 좋은 선택입니다.apps/web/src/components/common/DNDController/hooks/useDragAndDrop.ts (1)
88-88
: 상태 업데이트 로직이 개선되었습니다!상태 업데이트 후 바로 정렬된 아이템을 반환하도록 수정된 것이 좋습니다. 이는 코드의 일관성과 예측 가능성을 높여줍니다.
Also applies to: 110-110
apps/web/src/components/common/DNDController/compounds/index.ts (1)
1-2
: 컴포넌트 구조 변경 검토ContentItem의 경로가 더 구체적인 위치로 이동되었고, DraggableContentItem이 DraggableItem으로 대체되었습니다. 이러한 변경은 컴포넌트의 재사용성과 유지보수성을 향상시킬 것으로 보입니다.
apps/web/src/components/common/DNDController/DndController.tsx (1)
6-6
: DndController 컴포넌트 업데이트 확인DraggableContentItem에서 DraggableItem으로의 변경이 일관되게 적용되었습니다. 이는 compounds 디렉토리의 변경사항과 잘 동기화되어 있습니다.
Also applies to: 25-25
apps/web/src/components/common/DNDController/compounds/DraggableItem/DraggableItem.tsx (1)
5-7
: 타입 정의 개선사항 검토Props 타입이 더 유연하게 개선되었습니다:
- id 타입이
number | string
으로 확장되어 더 다양한 사용 사례를 지원합니다- ComponentPropsWithoutRef를 활용하여 HTML div 요소의 props를 적절히 상속받습니다
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/_components/EditContent/EditContent.tsx (1)
19-19
: 컴포넌트 재사용성 개선이 잘 이루어졌습니다!
ContentItem
컴포넌트를 도입하여 중복 코드를 제거하고 일관된 UI를 제공하도록 개선되었습니다.Also applies to: 127-135, 193-201, 228-235
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/detail/_components/EditSidebar/EditSidebar.tsx (2)
48-48
: TODO 주석들을 이슈로 트래킹해야 합니다.여러 TODO 주석들이 있습니다:
- param 주입 방식 수정
- 스켈레톤 데이터 관련
- 중복되는 로직 컴포넌트화
- 제어 컴포넌트 수정
이러한 개선 사항들을 이슈로 생성하여 체계적으로 관리하면 좋겠습니다.
이슈들을 생성해드릴까요?
Also applies to: 96-96, 106-106, 122-122
320-325
: 드래그 오버레이 구현이 잘 되었습니다!
ContentItem
을 활용하여 드래그 중인 아이템의 시각적 피드백을 일관성 있게 제공하고 있습니다.apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TitleWithDescription/TitleWithDescription.css.ts (1)
4-9
: 스타일 구현이 깔끔합니다!flex 레이아웃을 사용한 구현이 적절하며, 테마 변수를 일관되게 활용하고 있습니다.
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.css.ts (1)
12-44
: 테이블 셀과 헤더 스타일이 적절히 구성되어 있습니다.플렉스 레이아웃을 사용한 정렬과
flexShrink: 0
을 통한 크기 유지가 잘 되어 있습니다.apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/page.tsx (1)
7-20
: 서버 사이드 데이터 페칭이 적절하게 구현되어 있습니다.
ServerFetchBoundary
를 활용한 데이터 페칭 처리와 토큰 관리가 잘 되어 있습니다.apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/ScheduleTable/ScheduleTable.css.ts (1)
27-44
: 헤더 셀 스타일이 상세하게 정의되어 있습니다.텍스트 스타일과 패딩 처리가 디자인 시스템을 잘 따르고 있습니다.
apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.tsx (1)
9-11
: 타입 정의에 필수 컬럼 개수를 명시하세요.columns 배열의 최소 길이가 명시되어 있지 않아 런타임 에러가 발생할 수 있습니다.
type TableRowProps = Omit<ContentItemProps, 'dragListeners'> & { - columns: Column[]; + columns: [Column, Column, Column, Column]; // 정확히 4개의 컬럼이 필요함을 명시 } & Post;✅ Verification successful
컬럼 길이 정의 필요성 검증 완료
현재 코드에서는 columns가 임의 길이의 배열로 정의되어 있어, 4개의 컬럼이 반드시 필요할 경우 런타임 에러의 위험이 존재합니다. 제안하신 수정안처럼 columns 타입을 튜플 [Column, Column, Column, Column]으로 변경하면, 4개의 컬럼만이 허용되어 보다 안전한 타입 검증을 할 수 있습니다.
• 파일 위치: apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/_components/TableRow/TableRow.tsx
• 변경 제안은 columns의 타입을 배열(Column[])에서 4개의 Column으로 구성된 튜플로 명시하여, 런타임 에러 가능성을 줄이는데 효과적입니다.apps/web/src/app/(prompt)/edit/[agentId]/[postGroupId]/schedule/Schedule.tsx (2)
1-18
: LGTM! 임포트 구성이 잘 되어있습니다.필요한 모든 컴포넌트와 훅이 적절하게 임포트되어 있습니다.
88-89
: TODO 주석 처리 필요드롭다운 컴포넌트 구현이 필요한 상태입니다.
@kongnayeon님이 구현하실 예정이라고 PR 설명에 명시되어 있습니다. 해당 작업이 완료될 때까지 이 PR의 머지를 보류해야 할지 확인이 필요합니다.
<div | ||
className={style.tableRowCellStyle} | ||
style={{ width: columns?.[0]?.width ?? 'auto' }} | ||
> | ||
드롭다운 | ||
</div> | ||
<div | ||
className={style.tableRowCellStyle} | ||
style={{ width: columns?.[1]?.width ?? 'auto' }} | ||
> | ||
드롭다운 | ||
</div> | ||
<div | ||
className={style.tableRowCellStyle} | ||
style={{ width: columns?.[2]?.width ?? 'auto' }} | ||
> | ||
드롭다운 | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
반복되는 셀 렌더링 로직을 리팩토링하세요.
동일한 패턴의 셀이 3번 반복되고 있습니다. 이를 map 함수를 사용하여 리팩토링하면 코드의 중복을 줄일 수 있습니다.
+const DROPDOWN_CELLS_COUNT = 3;
export function TableRow({ columns, ...contentItemProps }: TableRowProps) {
return (
<div className={style.tableRowStyle}>
- <div
- className={style.tableRowCellStyle}
- style={{ width: columns?.[0]?.width ?? 'auto' }}
- >
- 드롭다운
- </div>
- // ... 반복되는 셀들
+ {Array.from({ length: DROPDOWN_CELLS_COUNT }).map((_, index) => (
+ <div
+ key={index}
+ className={style.tableRowCellStyle}
+ style={{ width: columns?.[index]?.width ?? 'auto' }}
+ >
+ 드롭다운
+ </div>
+ ))}
<div className={style.contentItemWrapper}>
<ContentItem {...contentItemProps} />
</div>
</div>
);
}
📝 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.
<div | |
className={style.tableRowCellStyle} | |
style={{ width: columns?.[0]?.width ?? 'auto' }} | |
> | |
드롭다운 | |
</div> | |
<div | |
className={style.tableRowCellStyle} | |
style={{ width: columns?.[1]?.width ?? 'auto' }} | |
> | |
드롭다운 | |
</div> | |
<div | |
className={style.tableRowCellStyle} | |
style={{ width: columns?.[2]?.width ?? 'auto' }} | |
> | |
드롭다운 | |
</div> | |
const DROPDOWN_CELLS_COUNT = 3; | |
export function TableRow({ columns, ...contentItemProps }: TableRowProps) { | |
return ( | |
<div className={style.tableRowStyle}> | |
{Array.from({ length: DROPDOWN_CELLS_COUNT }).map((_, index) => ( | |
<div | |
key={index} | |
className={style.tableRowCellStyle} | |
style={{ width: columns?.[index]?.width ?? 'auto' }} | |
> | |
드롭다운 | |
</div> | |
))} | |
<div className={style.contentItemWrapper}> | |
<ContentItem {...contentItemProps} /> | |
</div> | |
</div> | |
); | |
} |
modal.confirm({ | ||
title: '정말 삭제하시겠어요?', | ||
description: '삭제된 글은 복구할 수 없어요', | ||
icon: <Modal.Icon name="notice" color="warning500" />, | ||
confirmButton: '삭제하기', | ||
cancelButton: '취소', | ||
confirmButtonProps: { | ||
onClick: () => { | ||
deletePost(postId); | ||
}, | ||
}, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
모달 텍스트를 상수로 분리하고 에러 처리를 추가하세요.
모달의 텍스트가 하드코딩되어 있으며, 삭제 작업의 에러 처리가 누락되어 있습니다.
+const DELETE_MODAL_TEXTS = {
+ title: '정말 삭제하시겠어요?',
+ description: '삭제된 글은 복구할 수 없어요',
+ confirm: '삭제하기',
+ cancel: '취소',
+} as const;
const handleDeletePost = (postId: Post['id']) => {
modal.confirm({
- title: '정말 삭제하시겠어요?',
- description: '삭제된 글은 복구할 수 없어요',
+ title: DELETE_MODAL_TEXTS.title,
+ description: DELETE_MODAL_TEXTS.description,
icon: <Modal.Icon name="notice" color="warning500" />,
- confirmButton: '삭제하기',
- cancelButton: '취소',
+ confirmButton: DELETE_MODAL_TEXTS.confirm,
+ cancelButton: DELETE_MODAL_TEXTS.cancel,
confirmButtonProps: {
onClick: () => {
- deletePost(postId);
+ deletePost(postId, {
+ onError: (error) => {
+ modal.alert({
+ title: '삭제 실패',
+ description: '게시글 삭제 중 오류가 발생했습니다.',
+ });
+ },
+ });
},
},
});
};
📝 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.
modal.confirm({ | |
title: '정말 삭제하시겠어요?', | |
description: '삭제된 글은 복구할 수 없어요', | |
icon: <Modal.Icon name="notice" color="warning500" />, | |
confirmButton: '삭제하기', | |
cancelButton: '취소', | |
confirmButtonProps: { | |
onClick: () => { | |
deletePost(postId); | |
}, | |
}, | |
}); | |
}; | |
const DELETE_MODAL_TEXTS = { | |
title: '정말 삭제하시겠어요?', | |
description: '삭제된 글은 복구할 수 없어요', | |
confirm: '삭제하기', | |
cancel: '취소', | |
} as const; | |
const handleDeletePost = (postId: Post['id']) => { | |
modal.confirm({ | |
title: DELETE_MODAL_TEXTS.title, | |
description: DELETE_MODAL_TEXTS.description, | |
icon: <Modal.Icon name="notice" color="warning500" />, | |
confirmButton: DELETE_MODAL_TEXTS.confirm, | |
cancelButton: DELETE_MODAL_TEXTS.cancel, | |
confirmButtonProps: { | |
onClick: () => { | |
deletePost(postId, { | |
onError: (error) => { | |
modal.alert({ | |
title: '삭제 실패', | |
description: '게시글 삭제 중 오류가 발생했습니다.', | |
}); | |
}, | |
}); | |
}, | |
}, | |
}); | |
}; |
{onRemove && <IconButton icon="trash" onClick={onRemove} />} | ||
{onModify && <IconButton icon="pencil" onClick={onModify} />} | ||
{dragListeners && ( | ||
<IconButton | ||
className={cursorGrabStyle} | ||
icon="sixDots" | ||
{...dragListeners} | ||
/> | ||
)} | ||
<IconButton icon="sixDots" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
접근성을 위한 aria-label을 추가하세요.
IconButton들에 aria-label이 누락되어 있어 스크린 리더 사용자의 접근성이 저하됩니다.
- {onRemove && <IconButton icon="trash" onClick={onRemove} />}
- {onModify && <IconButton icon="pencil" onClick={onModify} />}
- <IconButton icon="sixDots" />
+ {onRemove && <IconButton icon="trash" onClick={onRemove} aria-label="콘텐츠 삭제" />}
+ {onModify && <IconButton icon="pencil" onClick={onModify} aria-label="콘텐츠 수정" />}
+ <IconButton icon="sixDots" aria-label="드래그 핸들" />
📝 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.
{onRemove && <IconButton icon="trash" onClick={onRemove} />} | |
{onModify && <IconButton icon="pencil" onClick={onModify} />} | |
{dragListeners && ( | |
<IconButton | |
className={cursorGrabStyle} | |
icon="sixDots" | |
{...dragListeners} | |
/> | |
)} | |
<IconButton icon="sixDots" /> | |
{onRemove && <IconButton icon="trash" onClick={onRemove} aria-label="콘텐츠 삭제" />} | |
{onModify && <IconButton icon="pencil" onClick={onModify} aria-label="콘텐츠 수정" />} | |
<IconButton icon="sixDots" aria-label="드래그 핸들" /> |
<MainBreadcrumbItem href="/" /> | ||
<Breadcrumb.Item active>기초 경제 지식</Breadcrumb.Item> | ||
</Breadcrumb> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
브레드크럼 텍스트 동적 처리 필요
브레드크럼의 텍스트가 하드코딩되어 있습니다.
다음과 같이 동적으로 처리하는 것을 제안드립니다:
- <Breadcrumb.Item active>기초 경제 지식</Breadcrumb.Item>
+ <Breadcrumb.Item active>{posts.data.title}</Breadcrumb.Item>
📝 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.
<MainBreadcrumbItem href="/" /> | |
<Breadcrumb.Item active>기초 경제 지식</Breadcrumb.Item> | |
</Breadcrumb> | |
<MainBreadcrumbItem href="/" /> | |
<Breadcrumb.Item active>{posts.data.title}</Breadcrumb.Item> | |
</Breadcrumb> |
<Button | ||
type="submit" | ||
size="large" | ||
variant="primary" | ||
leftAddon={<Icon name="check" size={20} />} | ||
onClick={() => {}} | ||
className={style.submitButtonStyle} | ||
> | ||
예약 완료 | ||
</Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제출 버튼 핸들러 구현 필요
예약 완료 버튼의 onClick
핸들러가 비어있습니다.
다음과 같이 핸들러를 구현하는 것을 제안드립니다:
- onClick={() => {}}
+ onClick={() => {
+ // TODO: 예약 완료 로직 구현
+ // 1. 현재 상태 유효성 검증
+ // 2. API 호출
+ // 3. 성공/실패 처리
+ // 4. 적절한 페이지로 리다이렉션
+ }}
📝 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.
<Button | |
type="submit" | |
size="large" | |
variant="primary" | |
leftAddon={<Icon name="check" size={20} />} | |
onClick={() => {}} | |
className={style.submitButtonStyle} | |
> | |
예약 완료 | |
</Button> | |
<Button | |
type="submit" | |
size="large" | |
variant="primary" | |
leftAddon={<Icon name="check" size={20} />} | |
onClick={() => { | |
// TODO: 예약 완료 로직 구현 | |
// 1. 현재 상태 유효성 검증 | |
// 2. API 호출 | |
// 3. 성공/실패 처리 | |
// 4. 적절한 페이지로 리다이렉션 | |
}} | |
className={style.submitButtonStyle} | |
> | |
예약 완료 | |
</Button> |
export default function Schedule({ params }: SchedulePageProps) { | ||
const [scrollRef, isScrolled] = useScroll<HTMLDivElement>({ threshold: 100 }); | ||
const { data: posts } = useGetAllPostsQuery(params); | ||
const { mutate: updatePosts } = useUpdatePostsMutation(params); | ||
const router = useRouter(); | ||
|
||
const readyToUploadPosts = posts.data.posts.filter( | ||
(post) => post.status === POST_STATUS.READY_TO_UPLOAD | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
데이터 페칭 에러 처리 추가 필요
데이터 페칭 과정에서 발생할 수 있는 에러 상황에 대한 처리가 누락되어 있습니다.
다음과 같이 에러 처리를 추가하는 것을 제안드립니다:
- const { data: posts } = useGetAllPostsQuery(params);
+ const { data: posts, error } = useGetAllPostsQuery(params);
+
+ if (error) {
+ return <ErrorComponent message="포스트를 불러오는 중 오류가 발생했습니다" />;
+ }
📝 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.
export default function Schedule({ params }: SchedulePageProps) { | |
const [scrollRef, isScrolled] = useScroll<HTMLDivElement>({ threshold: 100 }); | |
const { data: posts } = useGetAllPostsQuery(params); | |
const { mutate: updatePosts } = useUpdatePostsMutation(params); | |
const router = useRouter(); | |
const readyToUploadPosts = posts.data.posts.filter( | |
(post) => post.status === POST_STATUS.READY_TO_UPLOAD | |
); | |
export default function Schedule({ params }: SchedulePageProps) { | |
const [scrollRef, isScrolled] = useScroll<HTMLDivElement>({ threshold: 100 }); | |
const { data: posts, error } = useGetAllPostsQuery(params); | |
if (error) { | |
return <ErrorComponent message="포스트를 불러오는 중 오류가 발생했습니다" />; | |
} | |
const { mutate: updatePosts } = useUpdatePostsMutation(params); | |
const router = useRouter(); | |
const readyToUploadPosts = posts.data.posts.filter( | |
(post) => post.status === POST_STATUS.READY_TO_UPLOAD | |
); | |
// ... (rest of the component code) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 동작 제대로 하는 것 확인했고 다른 페이지들에서도 이상 없는 것 같아요! dnd 모듈 처음 만들어 주실 때도 잘 만들어 주셔서 수정 사항이 별로 없었던 것 같습니다! 깔끔하게 수정해 주셔서 감사해요! 너무 고생하셨습니다 ㅠㅠ!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
드롭다운 넣으면서 반영하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 생각보다 코드 수정이 많이 필요없었군요 굿... 👍🏻
관련 이슈
close: #116
변경 사항
업로드 예약 페이지 일부 퍼블리싱 및 dnd 관련 모듈 수정을 진행했어요.
dnd 의 Item 컴포넌트에 자식으로 자유롭게 컴포넌트를 넣고, dnd overlay에도 이에 맞게 자유롭게 컴포넌트를 넣을 수 있도록 기존 ContentItem과의 결합을 분리했어요. (+ 결과 수정 > 상세 페이지에서도 반영해두었습니다!)
그리고, @kongnayeon 님께서 진행해주실 Dropdown 컴포넌트를 넣는 것을 제외한 퍼블리싱을 진행했어요.
레퍼런스
Summary by CodeRabbit