-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 플로팅 버튼 개발 #108
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: 플로팅 버튼 개발 #108
Conversation
WalkthroughThis change introduces a floating widget feature to the home page. It adds a new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant FloatingButton
participant FloatingWidget
participant FloatingPanel
User->>FloatingButton: Clicks button
FloatingButton->>FloatingWidget: onClick (toggle open)
alt Panel is opened
FloatingWidget->>FloatingPanel: Render panel with tabs
User->>FloatingPanel: Interacts with tab triggers
FloatingPanel->>FloatingPanel: Switches tab content
end
User->>FloatingWidget: Clicks outside widget
FloatingWidget->>FloatingWidget: Detects click outside
FloatingWidget->>FloatingPanel: Closes panel
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 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
🧹 Nitpick comments (12)
src/hooks/useClickOutside.tsx (1)
1-23: Well-structured hook for detecting outside clicksThe implementation correctly handles both mouse and touch events, properly cleans up event listeners, and has appropriate TypeScript typing. This is a common pattern for implementing modals, dropdowns, and other dismissible UI elements.
One minor suggestion: since this file doesn't contain JSX, consider using a
.tsextension instead of.tsx.src/components/app/home/floating/FloatingPanelLayout.tsx (2)
7-23: Consider improving responsiveness for different screen sizesThe component uses fixed dimensions that may not adapt well to all devices:
width: 390pxcould overflow on smaller mobile screens- Fixed positioning (
bottom: 9rem; right: 1rem) may need adjustments on different viewportsConsider using responsive units (%, rem) or media queries for better adaptability across devices.
- position: absolute; - bottom: 9rem; - right: 1rem; + position: absolute; + bottom: min(9rem, 15%); + right: min(1rem, 5%);- width: 390px; + width: min(390px, 90vw);
15-23: Add z-index to prevent potential stacking context issuesThe floating panel might overlap with other elements on the page. Adding a z-index would ensure it appears above other content.
background-color: white; padding: 1.5rem; border-radius: 24px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); border: 1px solid #e5e7eb; position: relative; + z-index: 10;src/hooks/useTab.tsx (2)
73-89: Consider extracting hardcoded colors to theme variablesThe tab styling uses hardcoded color values (
#3076d4,#8e8e8e) which might make it difficult to maintain consistent styling across the application. Consider extracting these to theme constants or CSS variables.- color: ${isActive ? "#3076d4" : "#8e8e8e"}; + color: ${isActive ? "var(--color-primary)" : "var(--color-text-secondary)"}; // Later in the CSS - background-color: ${isActive ? "#3076d4" : "transparent"}; + background-color: ${isActive ? "var(--color-primary)" : "transparent"};
45-59: Consider a more flexible tab distribution methodUsing
justify-content: space-aroundfor tab distribution might not work optimally when there are many tabs. Consider usingjustify-content: space-betweenor a more flexible approach like CSS Grid for better space distribution.- justify-content: space-around; + justify-content: space-between;src/app/home/index.tsx (3)
30-30: Fix import casing for consistencyThe import statement uses a lowercase first letter for
floatingWidgetwhile React components should use PascalCase naming conventions.-import FloatingWidget from "@/components/app/home/floating/floatingWidget"; +import FloatingWidget from "@/components/app/home/floating/FloatingWidget";
508-508: Add positioning or container for FloatingWidgetThe
FloatingWidgetcomponent is added directly to the section without any positioning context. This may cause layout issues depending on how the component is implemented.Consider wrapping it in a positioned container:
-<FloatingWidget /> +<div + css={css` + position: relative; + width: 100%; + `} +> + <FloatingWidget /> +</div>
115-124: JSDoc function documentation has a typoThe JSDoc comment for
openSubscriptionModalhas a spacing issue in "flowbit서비스" (should be "flowbit 서비스")./** - * @description 구독버튼 클릭시 flowbit서비스에 대해 주기적으로 구독할 수 있는 함수입니다. + * @description 구독버튼 클릭시 flowbit 서비스에 대해 주기적으로 구독할 수 있는 함수입니다. */src/components/app/home/floating/floatingWidget.tsx (1)
1-28: Well-structured component with clean implementation!This floating widget implementation follows good React practices:
- Uses functional components with hooks for state management
- Employs custom hooks for toggle behavior and outside click detection
- Properly passes props and refs to child components
- Has clear separation of concerns between state management and rendering
Consider enhancing accessibility by adding ARIA attributes to help screen reader users understand this interactive element.
return ( <FloatingWidgetLayout containerRef={floatingRef}> {isOpen && <FloatingPanel />} - <FloatingButton isOpen={isOpen} onClick={toggleOpen} /> + <FloatingButton + isOpen={isOpen} + onClick={toggleOpen} + aria-expanded={isOpen} + aria-haspopup="true" + aria-label={isOpen ? "닫기" : "기능 메뉴 열기"} + /> </FloatingWidgetLayout> );src/components/app/home/floating/FloatingWidgetLayout.tsx (1)
1-42: Good layout component with proper ref handling.The layout implementation uses proper techniques:
- Correctly implements
forwardReffor ref handling- Properly positions the floating widget
- Sets a display name for debugging
- Resets button styles to avoid browser defaults
Consider making the position more responsive for mobile devices, as fixed positioning with specific rem values might cause display issues on smaller screens.
<div css={css` position: fixed; bottom: 5rem; right: 8rem; + @media (max-width: 768px) { + bottom: 2rem; + right: 2rem; + } button { background: transparent; border: none; padding: 0; margin: 0; outline: none; } `} >src/components/app/home/floating/FloatingPanel.tsx (2)
14-18: Consider extracting tab keys to a constants file.The tab key definitions would be more maintainable if extracted to a separate constants file, especially if they might be used elsewhere in the application.
Consider moving this to a separate constants file:
// src/constants/tabKeys.ts export const FloatingPanelTabKey = { CHART_ANALYZE: "CHART_ANALYZE", CHART_RECOMMENDATION: "CHART_RECOMMENDATION", FUNCTION_HELP: "FUNCTION_HELP", } as const; export type FloatingPanelTabKeyType = keyof typeof FloatingPanelTabKey;Then import and use it in this component.
1-12: Consider lazy-loading icon assets for performance.The component imports multiple SVG icons that might increase the initial bundle size. Consider lazy-loading these assets, especially if the floating panel isn't immediately visible when the page loads.
You could use dynamic imports with React.lazy and Suspense to load these icons on demand:
import { lazy, Suspense } from 'react'; import FloatingPanelLayout from "./FloatingPanelLayout"; import { useTab } from "@/hooks/useTab"; const IconAIChartAnalyzeInactive = lazy(() => import('@/assets/IconAIChartAnalyzeInactive.svg?react')); const IconAIChartAnalyzeActive = lazy(() => import('@/assets/IconAIChartAnalyzeActive.svg?react')); // Same for other icons // Then wrap icon usage in Suspense <Suspense fallback={<div>Loading...</div>}> {isActive ? <IconAIChartAnalyzeActive /> : <IconAIChartAnalyzeInactive />} </Suspense>This approach would be especially beneficial if the SVG files are large or if there are many of them.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
src/assets/IconAIChartAnalyzeActive.svgis excluded by!**/*.svgsrc/assets/IconAIChartAnalyzeInactive.svgis excluded by!**/*.svgsrc/assets/IconAIChartRecommendActive.svgis excluded by!**/*.svgsrc/assets/IconAIChartRecommendInactive.svgis excluded by!**/*.svgsrc/assets/IconFloatingClose.svgis excluded by!**/*.svgsrc/assets/IconFloatingOpen.svgis excluded by!**/*.svgsrc/assets/IconFunctionHelpActive.svgis excluded by!**/*.svgsrc/assets/IconFunctionHelpInactive.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
src/app/home/index.tsx(3 hunks)src/components/app/home/floating/AIChartAnalyze.tsx(1 hunks)src/components/app/home/floating/AIChartRecommendation.tsx(1 hunks)src/components/app/home/floating/FloatingButton.tsx(1 hunks)src/components/app/home/floating/FloatingPanel.tsx(1 hunks)src/components/app/home/floating/FloatingPanelLayout.tsx(1 hunks)src/components/app/home/floating/FloatingWidgetLayout.tsx(1 hunks)src/components/app/home/floating/FunctionHelp.tsx(1 hunks)src/components/app/home/floating/floatingWidget.tsx(1 hunks)src/hooks/useClickOutside.tsx(1 hunks)src/hooks/useTab.tsx(1 hunks)src/hooks/useToggle.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/app/home/floating/FloatingPanel.tsx (5)
src/hooks/useTab.tsx (1)
useTab(28-123)src/components/app/home/floating/FloatingPanelLayout.tsx (1)
FloatingPanelLayout(4-41)src/components/app/home/floating/AIChartAnalyze.tsx (1)
AIChartAnalyze(1-3)src/components/app/home/floating/AIChartRecommendation.tsx (1)
AIChartRecommendation(1-3)src/components/app/home/floating/FunctionHelp.tsx (1)
FunctionHelp(1-3)
src/components/app/home/floating/floatingWidget.tsx (4)
src/hooks/useToggle.ts (1)
useToggle(3-13)src/hooks/useClickOutside.tsx (1)
useClickOutside(3-23)src/components/app/home/floating/FloatingPanel.tsx (1)
FloatingPanel(13-62)src/components/app/home/floating/FloatingButton.tsx (1)
FloatingButton(9-18)
🔇 Additional comments (3)
src/hooks/useToggle.ts (1)
1-13: Well-implemented toggle hook with clean APIThis custom hook follows React best practices and provides a clean, type-safe API for managing toggle state. The three returned properties (value, toggleValue, and setValue) give the consumer complete control over the toggle state.
src/components/app/home/floating/FloatingPanelLayout.tsx (1)
26-37: Nice visual touch with the speech bubble pointerThe rotated square creates a nice visual cue pointing to the button that likely triggers this panel. Good attention to detail.
src/hooks/useTab.tsx (1)
28-123: Well-implemented compound component pattern for tabsThe hook provides a flexible, type-safe API for creating tabbed interfaces. The compound component pattern with static subcomponents (List, Trigger, Content, ContentWrapper) creates a clean, intuitive API for consumers.
| export default function FunctionHelp() { | ||
| return <div>FunctionHelp</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
Component implementation is a placeholder
This component is currently just a placeholder returning static text. As part of the floating panel's tabbed interface, it should be properly implemented with actual function help content.
Consider implementing the actual help content with proper styling and information about the application's features:
-export default function FunctionHelp() {
- return <div>FunctionHelp</div>;
+export default function FunctionHelp() {
+ return (
+ <div className="function-help-container">
+ <h3>기능 도움말</h3>
+ <ul>
+ <li>
+ <strong>차트 분석:</strong> 코인 데이터를 분석하여 추세를 파악할 수 있습니다.
+ </li>
+ <li>
+ <strong>AI 추천:</strong> AI 기반 코인 투자 추천을 확인할 수 있습니다.
+ </li>
+ {/* Add more help items as needed */}
+ </ul>
+ </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.
| export default function FunctionHelp() { | |
| return <div>FunctionHelp</div>; | |
| } | |
| export default function FunctionHelp() { | |
| return ( | |
| <div className="function-help-container"> | |
| <h3>기능 도움말</h3> | |
| <ul> | |
| <li> | |
| <strong>차트 분석:</strong> 코인 데이터를 분석하여 추세를 파악할 수 있습니다. | |
| </li> | |
| <li> | |
| <strong>AI 추천:</strong> AI 기반 코인 투자 추천을 확인할 수 있습니다. | |
| </li> | |
| {/* Add more help items as needed */} | |
| </ul> | |
| </div> | |
| ); | |
| } |
| export default function AIChartAnalyze() { | ||
| return <div>AIChartAnalyze</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
Component implementation is a placeholder
This component is currently just a placeholder with static text. Since this is part of the main AI feature set (차트해석/Chart Analysis), it should contain the actual implementation for chart analysis functionality.
Consider implementing the actual AI chart analysis content with proper UI components:
-export default function AIChartAnalyze() {
- return <div>AIChartAnalyze</div>;
+export default function AIChartAnalyze() {
+ return (
+ <div className="ai-chart-analyze">
+ <h3>AI 차트해석</h3>
+ <div className="chart-analysis-content">
+ {/* Implement the actual chart analysis UI here */}
+ <p>선택된 코인의 차트를 AI가 분석 중입니다...</p>
+ {/* Add loading state, analysis results, etc. */}
+ </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.
| export default function AIChartAnalyze() { | |
| return <div>AIChartAnalyze</div>; | |
| } | |
| export default function AIChartAnalyze() { | |
| return ( | |
| <div className="ai-chart-analyze"> | |
| <h3>AI 차트해석</h3> | |
| <div className="chart-analysis-content"> | |
| {/* Implement the actual chart analysis UI here */} | |
| <p>선택된 코인의 차트를 AI가 분석 중입니다...</p> | |
| {/* Add loading state, analysis results, etc. */} | |
| </div> | |
| </div> | |
| ); | |
| } |
| export default function AIChartRecommendation() { | ||
| return <div>AIChartRecommendation</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
Component implementation is a placeholder
This component is currently just a placeholder with static text. Since this is part of the main AI feature set (AI 추천판단/Chart Recommendation), it should contain the actual implementation for AI-based recommendation functionality.
Consider implementing the actual AI recommendation content:
-export default function AIChartRecommendation() {
- return <div>AIChartRecommendation</div>;
+export default function AIChartRecommendation() {
+ return (
+ <div className="ai-chart-recommendation">
+ <h3>AI 추천판단</h3>
+ <div className="recommendation-content">
+ {/* Implement the actual recommendation UI here */}
+ <p>현재 코인 시장에 대한 AI 추천을 분석 중입니다...</p>
+ {/* Add recommendation cards, analysis results, etc. */}
+ </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.
| export default function AIChartRecommendation() { | |
| return <div>AIChartRecommendation</div>; | |
| } | |
| export default function AIChartRecommendation() { | |
| return ( | |
| <div className="ai-chart-recommendation"> | |
| <h3>AI 추천판단</h3> | |
| <div className="recommendation-content"> | |
| {/* Implement the actual recommendation UI here */} | |
| <p>현재 코인 시장에 대한 AI 추천을 분석 중입니다...</p> | |
| {/* Add recommendation cards, analysis results, etc. */} | |
| </div> | |
| </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.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/app/home/index.tsx(4 hunks)
🧰 Additional context used
🪛 GitHub Check: build
src/app/home/index.tsx
[failure] 30-30:
Cannot find module '@/components/app/home/floating/FloatingWidget' or its corresponding type declarations.
🪛 GitHub Actions: Build Test
src/app/home/index.tsx
[error] 30-30: TypeScript error TS2307: Cannot find module '@/components/app/home/floating/FloatingWidget' or its corresponding type declarations.
🔇 Additional comments (2)
src/app/home/index.tsx (2)
122-122: LGTM! Good practice with trailing comma.Adding a trailing comma in object literals is good practice as it makes future additions cleaner and reduces diff noise.
509-509: LGTM! Appropriate placement of FloatingWidget.The FloatingWidget is properly placed as a direct child of the main section, which will allow it to appear at the bottom right corner of the home screen as intended.
Once the import issue is fixed, please verify that the floating button appears in the correct position and functions as expected.
| height: 4.5rem;import FloatingWidget from '../../components/app/home/floating/FloatingWidget'; | ||
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.
Remove erroneous import statement in CSS template literal.
There's an import statement incorrectly placed inside a CSS template literal for the button's height. This will cause styling issues and potentially runtime errors.
- height: 4.5rem;import FloatingWidget from '../../components/app/home/floating/FloatingWidget';
-
+ height: 4.5rem;
src/app/home/index.tsx
Outdated
| import { loginState } from "@/store/user"; | ||
| import { SubscriptionModalContent } from "@/components/common/modal/SubscriptionModalContent"; | ||
|
|
||
| import FloatingWidget from "@/components/app/home/floating/FloatingWidget"; |
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.
Fix import path for FloatingWidget component.
The build is failing because the import path is incorrect. According to the PR objectives, the floating button components are created in the app/home/floating directory, but you're importing from components/app/home/floating.
-import FloatingWidget from "@/components/app/home/floating/FloatingWidget";
+import FloatingWidget from "@/app/home/floating/FloatingWidget";Alternatively, if you've placed the component in the components directory instead of the app directory, make sure the file exists at that location.
📝 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.
| import FloatingWidget from "@/components/app/home/floating/FloatingWidget"; | |
| -import FloatingWidget from "@/components/app/home/floating/FloatingWidget"; | |
| +import FloatingWidget from "@/app/home/floating/FloatingWidget"; |
🧰 Tools
🪛 GitHub Check: build
[failure] 30-30:
Cannot find module '@/components/app/home/floating/FloatingWidget' or its corresponding type declarations.
🪛 GitHub Actions: Build Test
[error] 30-30: TypeScript error TS2307: Cannot find module '@/components/app/home/floating/FloatingWidget' or its corresponding type declarations.
klmhyeonwoo
left a comment
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.
컴파운드 패턴이랑 추상화 너무너무 좋네요!
조금의 코멘트를 남겨보았습니다, 너무너무 고생하셨습니다!
| CHART_RECOMMENDATION: "CHART_RECOMMENDATION", | ||
| FUNCTION_HELP: "FUNCTION_HELP", | ||
| } as const; | ||
|
|
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.
const tabItems = [
{
key: TabKey.CHART_ANALYZE,
label: "AI 차트해석",
activeIcon: <IconAIChartAnalyzeActive />,
inactiveIcon: <IconAIChartAnalyzeInactive />,
content: <AIChartAnalyze />,
},
{
key: TabKey.CHART_RECOMMENDATION,
label: "AI 추천판단",
activeIcon: <IconAIChartRecommendActive />,
inactiveIcon: <IconAIChartRecommendInactive />,
content: <AIChartRecommendation />,
},
...
];이렇게 위와 같이 중복적으로 사용되는 값들을 변수로 구조화하게 된다면 추후 수정 사항이 발생했을 때 변수 구조 부분만 살짝 바꾸면서 관련 컴포넌트들을 Map 형태로 쉽게 나타낼 수 있을 것 같다는 생각이 드는데 어떻게 생각하시나영!
<FloatingPanelLayout>
<Tabs>
<Tabs.List>
{tabItems.map(({ key, label, activeIcon, inactiveIcon }) => (
<Tabs.Trigger
key={key}
value={key}
activeIcon={activeIcon}
inactiveIcon={inactiveIcon}>
{label}
</Tabs.Trigger>
))}
</Tabs.List>
<Tabs.ContentWrapper>
{tabItems.map(({ key, content }) => (
<Tabs.Content key={key} value={key}>
{content}
</Tabs.Content>
))}
</Tabs.ContentWrapper>
</Tabs>
</FloatingPanelLayout>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.
넵! 말씀하신 코드 리뷰를 바탕으로 제가 작성한 코드를 다시 보니, 새로운 탭에 수정이 생기거나 삭제 또는 추가가 발생할 경우에 어려움이 있을 것 같다고 생각했어요!
따라서 코드리뷰를 반영하여 tabItems를 만들는 방식으로 코드를 리팩토링 하였습니다. 이렇게 수정해보니 결과적으로 전체 흐름을 이전보다 깔끔하게 볼 수 있어서 좋은 것 같고 수정이 발생할 때에도 변경이 쉬워질 것 같아서 좋은 것 같아요!
|
|
||
| export default function FloatingPanelLayout({ children }: PropsWithChildren) { | ||
| return ( | ||
| <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.
지금 플로우빗 기존 코드에서 느끼고 있는 아쉬운 점들은 모두 엘리먼트에 인라인으로 css props 형태로 스타일이 적용되고 있는 부분인데 이 부분을 새롭게 만드는 컴포넌트부터는 변수 처리하여 하단으로 영역 구분을 하는 것은 어떨까요?!
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.
이 부분을 제가 놓치고 있었네요. FloatingPanelLayout.tsx 뿐만 아니라 FloatingWidgetLayout.tsx도 css 적용을 리팩토링 했습니다. 아래에 스타일 관련 코드를 변수명에 넣는 방식으로 수정했어요!
joeunSong
left a comment
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.
고생 많으셨습니다!!
목적
작업 내용
app/home/floating
hooks/useToggle.ts
hooks/useTab.tsx
플로팅 버튼에 대한 명칭을 FloatingWidget으로 하고 해당 위젯은 FloatingPanel.tsx과 FloatingButton.tsx를 포함하도록 하였습니다.
FloatingPanel.tsx의 경우에는 useTab훅을 사용하여 탭에 대한 로직을 추상화 하였습니다.
필수 리뷰어
이슈 번호
비고
Summary by CodeRabbit
Summary by CodeRabbit