[Deploy] develop → main 배포 반영 - #243
Conversation
📝 WalkthroughWalkthrough대규모 플랫폼 통합 변경사항으로 Kakao 플랫폼을 Meta로 전환하고, AI 광고 성과 분석 기능을 새로 추가하며, 역할 기반 접근 제어 시스템을 도입하고, 대시보드 메트릭을 개선하며, UI 스타일을 통일합니다. Changes플랫폼 타입 중앙화 및 Kakao→Meta 전환
역할 기반 접근 제어(RBAC) 시스템
AI 광고 성과 분석 기능
대시보드 메트릭 및 일별 성과 개선
UI 개선 및 스타일 통일
Sequence DiagramsequenceDiagram
participant User
participant UI as Dashboard UI
participant AiAnalysis as useAiAnalysisReport
participant API as AI Analysis API
participant Store as Workspace Store
User->>UI: AI 분석 요청
UI->>AiAnalysis: requestAnalysis()
AiAnalysis->>API: POST /analysis
API-->>AiAnalysis: accessToken (202)
AiAnalysis->>API: GET /reports/{token}
loop Polling (PENDING)
API-->>AiAnalysis: status=PENDING
AiAnalysis->>AiAnalysis: Wait refetchInterval
end
API-->>AiAnalysis: status=SUCCESS, result
AiAnalysis->>UI: reportData
UI->>User: 분석 결과 표시
User->>UI: PDF 저장
UI->>UI: renderToStaticMarkup
UI->>UI: downloadAiSummaryPdf
UI->>User: PDF 다운로드
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/dashboard/platform/AllPlatformTrafficChart.tsx (1)
24-41:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPROVIDER_TYPES와 platformTrafficMock 키를 타입 레벨로 동기화하세요
- 지금 mock(
platformTrafficMock)은GOOGLE/NAVER/META를 모두 들고 있어서 이 코드 경로에서 즉시 크래시 가능성은 낮아요.- 다만
platformTrafficMock이Record<string, IClickStreamResponse>라서 TS가 “키가 항상 존재”한다고 가정합니다. 향후PROVIDER_TYPES에 값이 추가/변경되면 실제로platformTrafficMock[platform]이undefined가 되어data.timeSeriesData에서 런타임 에러가 날 수 있어요.platformTrafficMock타입을Record<TProviderType, IClickStreamResponse>로 바꿔 키 불일치를 컴파일 타임에 잡는 쪽을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx` around lines 24 - 41, The code assumes platformTrafficMock has every key in PROVIDER_TYPES but its type is currently Record<string, IClickStreamResponse>; change the mock's declaration to Record<TProviderType, IClickStreamResponse> (or a mapped type using the same TProviderType used by PROVIDER_TYPES) so TypeScript enforces key parity, import or reference TProviderType/IClickStreamResponse in the mock module and update the mock to include all providers (or make missing entries explicit), and then update usages in AllPlatformTrafficChart (PROVIDER_TYPES, platformTrafficMock) if needed to satisfy the narrowed types; optionally add a runtime guard (if (!data) return []) only if you prefer extra safety.
🧹 Nitpick comments (5)
src/components/landing/LandingHero.tsx (1)
46-46: 💤 Low value배경 오버레이에 텍스트 토큰 사용 검토
bg-text-400/64를 스크림 레이어에 사용하고 있는데,text-400는 일반적으로 텍스트 색상을 위한 토큰이야. 배경 오버레이 용도라면surface-*계열 토큰이나 별도의 scrim 전용 토큰을 고려해볼 수 있을 것 같아.현재 코드가 의도적으로
text-400를 사용하는 거라면 괜찮지만, tokens.css에서 해당 토큰의 용도를 확인해보는 게 좋을 것 같아.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/landing/LandingHero.tsx` at line 46, The overlay is using a text color token (bg-text-400/64) in LandingHero's scrim element (className "pointer-events-none absolute inset-0 z-1 bg-text-400/64"); replace it with an appropriate background/surface or scrim token (e.g., bg-surface-*/<alpha> or a dedicated scrim token) defined in tokens.css, or if the use of text-400 is intentional, add a brief inline comment near the className explaining why that token is chosen and confirm the token's intended purpose in tokens.css; ensure you update tokens.css if you introduce a new scrim token so the style system stays consistent.src/components/dashboard/ai-report/components/AiSummaryCard.tsx (1)
402-406: ⚡ Quick win펼침 토글에
aria-controls연결을 권장합니다.
AiSummaryExpandToggle은aria-expanded는 잘 지정했지만, 제어 대상 패널(${idPrefix}-panel)을 가리키는aria-controls가 없습니다. 패널motion.div에id를 부여하고 토글 버튼에서 동일 id를aria-controls로 참조하면 보조기술 사용자가 펼침/접힘 관계를 명확히 인지할 수 있습니다.♿ 제안 (id/ aria-controls 전달)
<AiSummaryExpandToggle cardTitle={title} isExpanded={isExpanded} onToggle={handleToggle} + panelId={`${idPrefix}-panel`} />
AiSummaryExpandToggle에panelId를 받아aria-controls={panelId}로,motion.div에는id={${idPrefix}-panel}을 추가해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx` around lines 402 - 406, Add an explicit relationship between the toggle and the collapsible panel by passing a panelId into AiSummaryExpandToggle and referencing it with aria-controls; update the parent panel (the motion.div that renders the summary details) to include id={`${idPrefix}-panel`} and pass the same id as panelId to AiSummaryExpandToggle so the toggle button (in AiSummaryExpandToggle) sets aria-controls={panelId} alongside the existing aria-expanded handling (keep isExpanded / handleToggle behavior unchanged).src/pages/workspace/WorkspaceSetting.tsx (1)
296-320: 권한 분기 처리 자체는 깔끔합니다. 다만 서버 측 검증도 함께 확인해 주세요.
isAdmin으로 저장/삭제 버튼을 숨기고 입력을 비활성화한 처리는 UX 측면에서 좋습니다. 다만 이는 클라이언트 가드일 뿐이라,updateWorkspace/deleteWorkspaceAPI 자체에서도 ADMIN 권한을 검증하지 않으면 비관리자가 직접 요청을 보내 우회할 수 있습니다. 백엔드에서 권한 검증이 이미 보장되는지 확인해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/workspace/WorkspaceSetting.tsx` around lines 296 - 320, Client-side isAdmin gating is fine but insufficient—ensure the server-side handlers for updateWorkspace and deleteWorkspace enforce ADMIN permission checks: update the API/controller functions named updateWorkspace and deleteWorkspace (and any auth middleware used by them) to verify the requesting user is an admin for the target workspace and return 403/unauthorized when not; ensure openDeleteModal/onSave callers remain unchanged, add unit/integration tests for non-admin requests hitting updateWorkspace/deleteWorkspace to confirm the server rejects them, and log/handle authorization failures consistently.src/components/dashboard/ai-report/print/printAssets.ts (1)
4-9: 💤 Low value문자열 치환 방식이 SVG 포맷에 의존적입니다.
replace("<svg ", ...)는 빌드 출력에서<svg가 줄바꿈이나 다른 첫 속성으로 시작하면 매칭에 실패합니다. 지금은 고정 자산이라 동작하지만, 로고 자산이 교체되거나 SVGO 설정이 바뀌면 속성 주입이 조용히 누락될 수 있어요. 정규식(/<svg\b/i)으로 바꾸면 포맷 변화에 더 견고해집니다.♻️ 제안
- .replace( - "<svg ", - '<svg aria-hidden="true" focusable="false" preserveAspectRatio="xMidYMid meet" ', - ); + .replace( + /<svg\b/i, + '<svg aria-hidden="true" focusable="false" preserveAspectRatio="xMidYMid meet"', + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/ai-report/print/printAssets.ts` around lines 4 - 9, The current replacement for injecting attributes into the logo SVG (AI_REPORT_LOGO_PRINT_SVG) relies on literal string match replace("<svg ", ...) which fails if the SVG tag has newlines or different spacing; update the transformation on serviceLogoSvg to use a case-insensitive word-boundary regex for the opening tag (e.g. /<svg\b/i) so the attribute injection is robust to formatting changes, and keep the existing fill replacement logic intact.src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts (1)
21-32: ⚡ Quick win파라미터명
document가 전역document를 섀도잉합니다.지금은 내부에서
window.document/printWindow.document로 일관되게 접근하고 있어 동작은 정상입니다. 다만 함수 스코프에서 전역document를 데이터 객체가 가려버리기 때문에, 이후 누군가 무심코document.createElement같은 코드를 추가하면 DOM API가 아닌 보고서 데이터를 참조하게 되는 함정이 생깁니다. 인자명을reportDocument(또는report)로 바꿔 두면 안전합니다.♻️ 제안
-export function downloadAiSummaryPdf(document: TAiReportPrintDocument) { +export function downloadAiSummaryPdf(reportDocument: TAiReportPrintDocument) { const reportMarkup = renderToStaticMarkup( - createElement(AiSummaryPrintReport, { document }), + createElement(AiSummaryPrintReport, { document: reportDocument }), );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts` around lines 21 - 32, The parameter named document in downloadAiSummaryPdf shadows the global DOM document; rename it (e.g., reportDocument or report) and update all references inside the function (for example the call createElement(AiSummaryPrintReport, { document }) and any other uses) to use the new parameter name, while leaving all accesses to window.document / printWindow.document unchanged so DOM calls still reference the global document.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/common/button/Button.tsx`:
- Around line 47-48: The gradient class string in Button.tsx uses the undefined
shadow token "shadow-Soft"; either add a corresponding CSS token in your tokens
file (e.g., define --shadow-Soft and map shadow-Soft in src/styles/tokens.css)
or replace "shadow-Soft" in the gradient value with an existing shadow token
name from tokens.css so the Button component's gradient class references a valid
shadow variable.
In `@src/components/common/card/Card.tsx`:
- Line 24: Card.tsx (and similar uses in Drawer.tsx and DropdownMenu.tsx)
currently use the utility class shadow-Soft which is defined only in
src/styles/utilities.css, and the expected hover/transition variants
(shadow-card, hover:shadow-card-hover, transition-shadow) are missing across the
codebase; confirm intended behavior and either (A) replace shadow-Soft in
Card.tsx/Drawer.tsx/DropdownMenu.tsx with the standardized classes (e.g.,
shadow-card plus hover:shadow-card-hover and transition-shadow) so hover shadow
changes work, or (B) move/alias the .shadow-Soft definition into the theme
tokens file (src/styles/tokens.css `@theme`) and add matching hover/transition
classes (.shadow-card, .shadow-card-hover, .transition-shadow) in CSS so
existing class names keep intended hover effect—update the three components
(Card.tsx, Drawer.tsx, DropdownMenu.tsx) to use the chosen standardized class
names.
In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx`:
- Around line 99-107: The heading levels in AiSummaryCard are semantically
skipped: replace the h5 element rendering the card title (where id, className,
isPrimary, title are used) with an h4 to restore proper document outline, and
likewise bump any h5→h4 and h6→h5 occurrences in this component (the other
headings around the same block referenced at the same file) while preserving the
visual style (keep the "font-heading4" className and existing twMerge logic and
isPrimary color logic); ensure ARIA/state props remain unchanged.
- Around line 348-351: The SparkleIcon instance uses a hard-coded fill class;
change its classes to use fill-current and the text utility for color so it
follows the project convention (e.g., update the SparkleIcon className to
include "fill-current text-primary-400" instead of "fill-primary-400"), and scan
other SVG components (icons) like the one following the same pattern to ensure
they use fill-current/stroke-current with text-* classes for color consistency.
- Around line 281-307: The collapse path can leave autoExpandOnResultRef.current
true (so future data re-expands the card); update handleToggle (the function
that calls setIsExpanded) so that when toggling to collapsed (next === false)
you always set autoExpandOnResultRef.current = false (rather than only when
isLoading), ensuring autoExpandOnResultRef is cleared whenever the user
collapses the card; keep the existing onExpand call and function dependencies
(data, isLoading, onExpand).
In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts`:
- Around line 54-71: The iframe cleanup currently only runs on printWindow's
"afterprint" event; make cleanup idempotent (safe to call multiple times) and
add a safety timeout fallback so the iframe is removed and the listener detached
even if "afterprint" never fires. Specifically, modify the cleanup function used
with printWindow.addEventListener("afterprint", cleanup) to check/guard whether
iframe is already removed and ensure it can be called repeatedly, add a
setTimeout that calls cleanup after a short timeout as a fallback, and add a
.catch handler to the printDoc.fonts.ready promise chain so runPrint still
proceeds on fonts.ready rejection (and still triggers the fallback cleanup
path).
In `@src/components/landing/LandingMultiDevice.tsx`:
- Around line 11-12: MOCKUP_OVERLAY_CLASS uses the legacy Tailwind gradient
utility `bg-gradient-to-t` which is not recognized under Tailwind v4; update the
class string in MOCKUP_OVERLAY_CLASS to use the v4 canonical utility
`bg-linear-to-t` so the overlay gradient renders correctly (modify the constant
MOCKUP_OVERLAY_CLASS in LandingMultiDevice.tsx to replace `bg-gradient-to-t`
with `bg-linear-to-t`).
In `@src/components/setting/NotificationSection.tsx`:
- Around line 68-79: The Slack integration block in NotificationSection.tsx is a
non-functional placeholder: the Button (variant="outline", size="small") has no
onClick and the channel label is hardcoded as "`#채널명`"; update the UI to avoid
user confusion by making the button disabled and adding a clear placeholder
status (e.g., "준비 중" or "미연동") or implement a minimal onClick stub that opens a
setup modal/handler; locate the SlackIcon + text block and the Button element in
NotificationSection and either set the Button to disabled and replace "`#채널명`"
with a dynamic placeholder/status string, or wire the Button to an onClick like
handleOpenSlackSetup to trigger the real setup flow.
In `@src/hooks/dashboard/useAiAnalysisReport.ts`:
- Around line 123-168: pollTimedOut currently uses useMemo with Date.now(),
which won't update while reportStatus stays "PENDING", so isPolling/isLoading
never reflect the timeout; change pollTimedOut to be time-driven (either compute
the boolean inline on every render using Date.now() - pollStartedAt >
MAX_POLL_MS or set up an effect that flips a pollTimedOut state after
MAX_POLL_MS when pollStartedAt/reportStatus become active) and ensure you
clear/reset that timer on reset; update references to pollTimedOut (used by
isPolling/isLoading) accordingly so the UI updates when the timeout elapses (use
symbols pollTimedOut, pollStartedAt, reportStatus, MAX_POLL_MS, reset,
isPolling, isLoading).
In `@src/pages/ads/new/CampaignGroup.tsx`:
- Around line 126-130: The MetaIcon in CampaignGroup.tsx currently has
className="h-6 w-6 shrink-0 text-text-title" but the inline SVG asset
(meta-circle.svg) uses hardcoded fills/gradients so the text-text-title utility
has no effect and is inconsistent with other icons; fix by removing the
text-text-title utility from the MetaIcon usage to match the other social icons
(or alternatively update the meta-circle.svg to use currentColor/var(--*) fills
and then keep the utility), ensuring consistency across icons and that color
control is actually applied via CSS or the SVG itself.
In `@src/pages/setting/Setting.tsx`:
- Around line 157-160: The current branch only updates local state
(setSavedNotification(draftNotification)) when hasNotificationChanges is true,
so the UI shows "saved" but changes are not persisted; implement a call to the
notification settings API (POST/PUT using your app's API client) when
hasNotificationChanges is true, send draftNotification payload, await the
response, only call setSavedNotification and show the success toast after a
successful server response, and on failure show an error toast and avoid or
revert the local update; use the existing symbols hasNotificationChanges,
draftNotification, setSavedNotification and add proper error handling and
loading state while the request is in flight (or if you opt not to implement
persistence now, change the toast copy to indicate "Locally updated" and
create/open an issue to track adding server persistence).
In `@src/pages/workspace/Workspace.tsx`:
- Around line 119-129: The useLayoutEffect currently calls setSearchParams({}, {
replace: true }) which wipes all query params; instead preserve existing params
and only remove the "create" key: inside the effect (the block using
searchParams, openedCreateFromQueryRef, onOpenCreate) clone the current
searchParams, delete the "create" entry, then call setSearchParams with that
updated params and { replace: true } so other query keys (filters, paging, etc.)
remain intact.
In `@src/styles/aiReport.print.css`:
- Line 10: Remove unnecessary quotes around the font family name in the
font-family declaration (change "Pretendard" to Pretendard) and normalize the
`@page` size keyword to lowercase (change A4 to a4) so the rules satisfy
stylelint's font-family-name-quotes and value-keyword-case checks; update the
font-family declaration and the `@page` size usage where they appear (the lines
containing the font-family: "Pretendard"; rule and the `@page` size: A4
declaration).
- Around line 50-51: The CSS uses deprecated properties (e.g., page-break-after
and other page-break-* usages) which trigger stylelint property-no-deprecated
errors; either remove those legacy properties if your target browsers support
the modern equivalents (e.g., keep break-after/break-inside only) or explicitly
exempt the legacy lines by adding a stylelint directive
(stylelint-disable-next-line property-no-deprecated) immediately above each
legacy declaration in src/styles/aiReport.print.css (identify occurrences by the
page-break-after / page-break-inside tokens) so the build no longer fails.
In `@src/styles/print.css`:
- Line 7: Replace the deprecated clip property usage with a non-deprecated
equivalent and normalize the page size keyword: locate the rule that uses clip:
rect(0, 0, 0, 0); and replace it with clip-path: inset(50%); (or clip-path:
inset(50% round 0) if you need to preserve rounding) to keep the same
screen-hide behavior and satisfy property-no-deprecated; also find the rule that
sets size: A4 and change the value to lowercase size: a4 to satisfy
value-keyword-case.
---
Outside diff comments:
In `@src/components/dashboard/platform/AllPlatformTrafficChart.tsx`:
- Around line 24-41: The code assumes platformTrafficMock has every key in
PROVIDER_TYPES but its type is currently Record<string, IClickStreamResponse>;
change the mock's declaration to Record<TProviderType, IClickStreamResponse> (or
a mapped type using the same TProviderType used by PROVIDER_TYPES) so TypeScript
enforces key parity, import or reference TProviderType/IClickStreamResponse in
the mock module and update the mock to include all providers (or make missing
entries explicit), and then update usages in AllPlatformTrafficChart
(PROVIDER_TYPES, platformTrafficMock) if needed to satisfy the narrowed types;
optionally add a runtime guard (if (!data) return []) only if you prefer extra
safety.
---
Nitpick comments:
In `@src/components/dashboard/ai-report/components/AiSummaryCard.tsx`:
- Around line 402-406: Add an explicit relationship between the toggle and the
collapsible panel by passing a panelId into AiSummaryExpandToggle and
referencing it with aria-controls; update the parent panel (the motion.div that
renders the summary details) to include id={`${idPrefix}-panel`} and pass the
same id as panelId to AiSummaryExpandToggle so the toggle button (in
AiSummaryExpandToggle) sets aria-controls={panelId} alongside the existing
aria-expanded handling (keep isExpanded / handleToggle behavior unchanged).
In `@src/components/dashboard/ai-report/print/downloadAiSummaryPdf.ts`:
- Around line 21-32: The parameter named document in downloadAiSummaryPdf
shadows the global DOM document; rename it (e.g., reportDocument or report) and
update all references inside the function (for example the call
createElement(AiSummaryPrintReport, { document }) and any other uses) to use the
new parameter name, while leaving all accesses to window.document /
printWindow.document unchanged so DOM calls still reference the global document.
In `@src/components/dashboard/ai-report/print/printAssets.ts`:
- Around line 4-9: The current replacement for injecting attributes into the
logo SVG (AI_REPORT_LOGO_PRINT_SVG) relies on literal string match replace("<svg
", ...) which fails if the SVG tag has newlines or different spacing; update the
transformation on serviceLogoSvg to use a case-insensitive word-boundary regex
for the opening tag (e.g. /<svg\b/i) so the attribute injection is robust to
formatting changes, and keep the existing fill replacement logic intact.
In `@src/components/landing/LandingHero.tsx`:
- Line 46: The overlay is using a text color token (bg-text-400/64) in
LandingHero's scrim element (className "pointer-events-none absolute inset-0 z-1
bg-text-400/64"); replace it with an appropriate background/surface or scrim
token (e.g., bg-surface-*/<alpha> or a dedicated scrim token) defined in
tokens.css, or if the use of text-400 is intentional, add a brief inline comment
near the className explaining why that token is chosen and confirm the token's
intended purpose in tokens.css; ensure you update tokens.css if you introduce a
new scrim token so the style system stays consistent.
In `@src/pages/workspace/WorkspaceSetting.tsx`:
- Around line 296-320: Client-side isAdmin gating is fine but
insufficient—ensure the server-side handlers for updateWorkspace and
deleteWorkspace enforce ADMIN permission checks: update the API/controller
functions named updateWorkspace and deleteWorkspace (and any auth middleware
used by them) to verify the requesting user is an admin for the target workspace
and return 403/unauthorized when not; ensure openDeleteModal/onSave callers
remain unchanged, add unit/integration tests for non-admin requests hitting
updateWorkspace/deleteWorkspace to confirm the server rejects them, and
log/handle authorization failures consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d76cdd86-6d7b-44b0-be3e-5fa73d933e51
⛔ Files ignored due to path filters (12)
.gitignoreis excluded by none and included by nonepackage.jsonis excluded by none and included by noneplaywright.config.tsis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by nonesrc/assets/icon/common/lightbulb.svgis excluded by!**/*.svgand included bysrc/**src/assets/logo/social-logo/plain/slack.svgis excluded by!**/*.svgand included bysrc/**tests/auth-guard.spec.tsis excluded by none and included by nonetests/find-email.spec.tsis excluded by none and included by nonetests/login.spec.tsis excluded by none and included by nonetests/navigation.spec.tsis excluded by none and included by nonetests/smoke.spec.tsis excluded by none and included by nonetsconfig.node.jsonis excluded by none and included by none
📒 Files selected for processing (93)
src/api/dashboard/aiAnalysis.tssrc/api/dashboard/platform.tssrc/components/ads/AdListTable.tsxsrc/components/ads/AdRow.tsxsrc/components/ads/CampaignInfoCard.tsxsrc/components/ads/CampaignRow.tsxsrc/components/ads/PlatformCard.tsxsrc/components/common/ComingSoonPlaceholder.tsxsrc/components/common/button/Button.tsxsrc/components/common/card/Card.tsxsrc/components/common/card/StatCard.tsxsrc/components/common/drawer/Drawer.tsxsrc/components/common/dropdownmenu/DropdownMenu.tsxsrc/components/common/modal/Modal.tsxsrc/components/common/select/SearchSelect.tsxsrc/components/common/toggle/Toggle.tsxsrc/components/dashboard/ai-report/components/AiSummaryCard.tsxsrc/components/dashboard/ai-report/components/DashboardAiSummarySection.tsxsrc/components/dashboard/ai-report/print/AiSummaryPrintReport.tsxsrc/components/dashboard/ai-report/print/downloadAiSummaryPdf.tssrc/components/dashboard/ai-report/print/printAssets.tssrc/components/dashboard/ai-report/utils/aiReport.utils.tssrc/components/dashboard/charts/PerformanceEfficiencyChart.tsxsrc/components/dashboard/overview/skeleton/OverviewSkeleton.tsxsrc/components/dashboard/platform/AllPlatformTrafficChart.tsxsrc/components/dashboard/platform/AllPlatformView.tsxsrc/components/dashboard/platform/PlatformDetailCard.tsxsrc/components/dashboard/platform/PlatformDetailTable.tsxsrc/components/dashboard/platform/PlatformRoasTable.tsxsrc/components/dashboard/platform/PlatformTrafficChart.tsxsrc/components/dashboard/platform/SinglePlatformView.tsxsrc/components/dashboard/platform/TopPerformanceList.tsxsrc/components/landing/GuideOverviewChart.tsxsrc/components/landing/GuidePlatform.tsxsrc/components/landing/GuideTimeline.tsxsrc/components/landing/LandingFAQ.tsxsrc/components/landing/LandingFeatures.tsxsrc/components/landing/LandingGuide.tsxsrc/components/landing/LandingHeader.tsxsrc/components/landing/LandingHero.tsxsrc/components/landing/LandingMultiDevice.tsxsrc/components/landing/LandingPricing.tsxsrc/components/setting/NotificationSection.tsxsrc/components/setting/ProfileSection.tsxsrc/components/setting/ProfileSectionSkeleton.tsxsrc/components/sidebar/Sidebar.tsxsrc/components/sidebar/WorkspaceSwitcher.tsxsrc/components/workspace/MemberManagementLoading.tsxsrc/components/workspace/WorkspaceCard.tsxsrc/components/workspace/WorkspaceListLoading.tsxsrc/constants/dashboard/overviewMetricsRange.tssrc/constants/sidebarNav.tssrc/hooks/ads/useCampaignGroup.tssrc/hooks/auth/useIsAdmin.tssrc/hooks/dashboard/useAiAnalysisReport.tssrc/hooks/dashboard/useOverviewRoasRankings.tssrc/hooks/dashboard/usePlatformBudget.tssrc/hooks/dashboard/usePlatformMetricFacts.tssrc/hooks/dashboard/usePlatformMetrics.tssrc/hooks/dashboard/usePlatformPerformance.tssrc/layout/main/MainLayout.tsxsrc/pages/ads/list/CampaignDetail.tsxsrc/pages/ads/new/CampaignGroup.tsxsrc/pages/dashboard/overview/OverviewAiDrawer.tsxsrc/pages/dashboard/overview/OverviewAiReportPanel.tsxsrc/pages/dashboard/overview/OverviewDashboard.tsxsrc/pages/dashboard/overview/aiReport.mock.tssrc/pages/dashboard/overview/sections/OverviewBudgetSection.tsxsrc/pages/dashboard/overview/sections/OverviewCampaignSnapshotCard.tsxsrc/pages/dashboard/overview/sections/OverviewKpiSection.tsxsrc/pages/dashboard/overview/sections/OverviewPlatformSection.tsxsrc/pages/dashboard/platform/PlatformDashboard.tsxsrc/pages/dashboard/platform/platformDashboard.mock.tssrc/pages/landing/LandingPage.tsxsrc/pages/setting/Setting.tsxsrc/pages/workspace/Workspace.tsxsrc/pages/workspace/WorkspaceSetting.tsxsrc/routes/MainRoutes.tsxsrc/routes/RoleGuard.tsxsrc/store/useWorkspaceStore.tssrc/stories/Shadows.stories.tsxsrc/stories/Typography.stories.tsxsrc/styles/aiReport.print.csssrc/styles/print.csssrc/styles/tokens.csssrc/styles/utilities.csssrc/types/ads/campaign.tssrc/types/dashboard/aiAnalysis.tssrc/types/dashboard/overview.tssrc/types/dashboard/platform.tssrc/types/dashboard/provider.tssrc/types/navigation/navItem.tssrc/vite-env.d.ts
💤 Files with no reviewable changes (6)
- src/components/ads/CampaignInfoCard.tsx
- src/pages/dashboard/overview/OverviewAiReportPanel.tsx
- src/components/ads/PlatformCard.tsx
- src/pages/dashboard/overview/aiReport.mock.ts
- src/pages/dashboard/overview/OverviewAiDrawer.tsx
- src/pages/dashboard/platform/platformDashboard.mock.ts
🚨 관련 이슈
#209 #213 #214 #220 #222 #225 #227 #228 #232
✨ 변경사항
✏️ 작업 내용
✨ Feature
myRole필드 추가 및mainLayout초기 로드 시 role 세팅, 워크스페이스 전환 시 role 갱신RoleGuard컴포넌트 구현 및 ADMIN 전용 라우터에 적용NavItem에requiredRole추가 + 사이드바 메뉴 필터링useIsAdmin훅 생성🐞 BugFix
hasChange로직 탭 구분 버그 수정accessTokenURL 인코딩 처리🔨 Refactor
overview폴더 구조 정리 (sections-ai-summary구조로 통합)provider.ts로 통합🎨 Design
shadow-Soft통일 및 위젯·대시보드 레이아웃 정리✅ Test
😅 미완성 작업
N/A
📢 논의 사항 및 참고 사항
Summary by CodeRabbit
주요 변경 사항
New Features
Platform Support
UI/Style