Repository navigation
[FIX] 시스템 대시보드 — 규칙 어긋난 색·여백·표 정비 #239 - #241
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough시스템 대시보드가 요금제 분포 대신 파이프라인 큐 데이터를 표시합니다. 요금제 기반 타입과 목 데이터를 제거합니다. 차트, 카드, 표의 시각 규격과 포커스 스타일을 조정합니다. 시스템 화면에는 쿠키 기반 범위별 테마를 적용합니다. Changes시스템 대시보드 개편
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant SystemLayout
participant ScopedThemeProvider
participant ThemeToggle
Browser->>SystemLayout: 시스템 화면 요청
SystemLayout->>ScopedThemeProvider: 쿠키 기반 initialDark 전달
ScopedThemeProvider->>Browser: 시스템 컨테이너에 dark 또는 light 적용
ThemeToggle->>ScopedThemeProvider: 범위 테마 전환
ScopedThemeProvider->>Browser: 테마 쿠키 저장 및 클래스 갱신
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/features/system/components/plan-distribution-card.tsx (1)
24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
PlanDistributionCardProps인터페이스를 분리하세요.현재
PlanDistributionCard는 인라인 객체 타입을 사용합니다. 명시적 props 인터페이스를 선언하고 함수 시그니처에서 사용하세요.As per coding guidelines,
src/**/*.{ts,tsx}는 명시적 props 인터페이스 사용을 요구합니다.🤖 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/features/system/components/plan-distribution-card.tsx` at line 24, PlanDistributionCard의 인라인 props 타입을 제거하고 PlanDistributionCardProps 인터페이스를 별도로 선언하세요. 인터페이스는 기존 data 필드의 타입인 PlanDistributionSlice[]를 유지하며, PlanDistributionCard 함수 시그니처에서 해당 인터페이스를 사용하세요.Source: Coding guidelines
src/features/system/components/signup-chart-card.tsx (1)
18-18: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win강제 색상 모드에서도 포커스 링을 보존하세요.
[&_.recharts-surface]:outline-none은 기본 외곽선을 완전히 제거합니다.forced-colors: active에서도:focus-visible링이 표시되는지 확인하세요. 표시되지 않으면 키보드 사용자가 차트 포커스를 확인할 수 없습니다.Tailwind CSS 4에서는 일반 외곽선을 숨기면서 강제 색상 모드 지원을 보존하려면
outline-hidden을 사용합니다. (tailwindcss.com)수정 예시
- [&_.recharts-surface]:outline-none + [&_.recharts-surface]:outline-hidden브라우저의 강제 색상 모드에서 Tab 키로 차트에 진입해 링을 검증하세요.
🤖 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/features/system/components/signup-chart-card.tsx` at line 18, Update the chart container’s Recharts surface outline utility to use Tailwind CSS 4’s outline-hidden behavior instead of [&_.recharts-surface]:outline-none, while preserving the existing focus-visible outline styles so the keyboard focus ring remains visible in forced-colors mode.Source: MCP tools
src/features/system/components/signup-chart.tsx (1)
59-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win툴팁 문구를 하드코딩하지 마세요.
"개사"와"신규 가입"은 UI 문구입니다. 영어 키를 사용하는constants/의as const레이블 맵으로 이동하고 이 컴포넌트에서는 맵을 참조하세요.수정 예시
- formatter={(value) => [`${value}개사`, "신규 가입"]} + formatter={(value) => [ + `${value}${SYSTEM_TOOLTIP_LABELS.companyUnit}`, + SYSTEM_TOOLTIP_LABELS.signup, + ]}As per coding guidelines:
Define domain value lists in constants/ using as const and label maps; use English constants in code and localized labels in the UI without hardcoded labels.🤖 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/features/system/components/signup-chart.tsx` at line 59, Move the tooltip labels “개사” and “신규 가입” into an appropriate constants/ as-const label map with English keys, then update the formatter in the signup chart component to reference that map instead of hardcoding UI text. Keep the existing tooltip value formatting and output structure unchanged.Source: Coding guidelines
🤖 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/app/`(system)/system/(dashboard)/page.tsx:
- Around line 46-49: Remove the ⚠️ emoji from the comments at
src/app/(system)/system/(dashboard)/page.tsx lines 46-49,
src/features/system/components/summary-card.tsx lines 17-19,
src/features/system/components/plan-distribution-card.tsx lines 6-8, 18-22, and
32-37, and src/features/system/components/plan-donut-chart-loader.tsx lines
12-13; replace it with plain-text warning wording or delete the comments while
preserving the surrounding implementation.
In `@src/features/system/components/plan-distribution-card.tsx`:
- Line 38: Update the chart container’s Recharts focus styles in the surrounding
div className: replace the default [&_.recharts-surface]:outline-none with
outline-hidden and add [&_.recharts-surface:focus-visible]:outline-solid,
preserving the existing focus-visible ring width, offset, and color utilities.
In `@src/features/system/components/recent-companies-table.tsx`:
- Around line 15-21: Update the column-padding documentation in the recent
companies table comment to state that the first and last columns use px-6, while
intermediate columns use px-4. Ensure the comment matches the classes used by
the name and 가입일 columns.
- Around line 48-61: Update all four TableHead elements in the recent-companies
table header row to include the h-9 height class, overriding the shared
TableHead default so each cell matches the TableRow height. Preserve their
existing styling and content.
---
Nitpick comments:
In `@src/features/system/components/plan-distribution-card.tsx`:
- Line 24: PlanDistributionCard의 인라인 props 타입을 제거하고 PlanDistributionCardProps
인터페이스를 별도로 선언하세요. 인터페이스는 기존 data 필드의 타입인 PlanDistributionSlice[]를 유지하며,
PlanDistributionCard 함수 시그니처에서 해당 인터페이스를 사용하세요.
In `@src/features/system/components/signup-chart-card.tsx`:
- Line 18: Update the chart container’s Recharts surface outline utility to use
Tailwind CSS 4’s outline-hidden behavior instead of
[&_.recharts-surface]:outline-none, while preserving the existing focus-visible
outline styles so the keyboard focus ring remains visible in forced-colors mode.
In `@src/features/system/components/signup-chart.tsx`:
- Line 59: Move the tooltip labels “개사” and “신규 가입” into an appropriate
constants/ as-const label map with English keys, then update the formatter in
the signup chart component to reference that map instead of hardcoding UI text.
Keep the existing tooltip value formatting and output structure unchanged.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: fb5c726f-2773-42d4-8105-4e9f0e006980
📒 Files selected for processing (8)
src/app/(system)/system/(dashboard)/page.tsxsrc/features/system/components/plan-distribution-card.tsxsrc/features/system/components/plan-donut-chart-loader.tsxsrc/features/system/components/plan-donut-chart.tsxsrc/features/system/components/recent-companies-table.tsxsrc/features/system/components/signup-chart-card.tsxsrc/features/system/components/signup-chart.tsxsrc/features/system/components/summary-card.tsx
| {/* | ||
| ⚠️ **곁 컬럼은 360px 고정**이다(DESIGN §1). 한때 `w-64`(256px)라 도넛이 눌려 있었고, | ||
| 카드 사이만 `gap-3`(12px)이라 위아래 `gap-7`(28px)과 리듬이 어긋났다. | ||
| */} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
변경된 TSX 주석에서 이모지를 제거하세요.
추가된 소스 주석에 ⚠️가 포함되어 있습니다. 이모지를 일반 텍스트 경고 문구로 바꾸거나 주석을 삭제하세요.
src/app/(system)/system/(dashboard)/page.tsx#L46-L49:⚠️를 제거하세요.src/features/system/components/summary-card.tsx#L17-L19:⚠️를 제거하세요.src/features/system/components/plan-distribution-card.tsx#L6-L8:⚠️를 제거하세요.src/features/system/components/plan-distribution-card.tsx#L18-L22:⚠️를 제거하세요.src/features/system/components/plan-distribution-card.tsx#L32-L37:⚠️를 제거하세요.src/features/system/components/plan-donut-chart-loader.tsx#L12-L13:⚠️를 제거하세요.
As per coding guidelines, src/**/*.{tsx,css}는 이모지 사용을 금지합니다.
📍 Affects 4 files
src/app/(system)/system/(dashboard)/page.tsx#L46-L49(this comment)src/features/system/components/summary-card.tsx#L17-L19src/features/system/components/plan-distribution-card.tsx#L6-L8src/features/system/components/plan-distribution-card.tsx#L18-L22src/features/system/components/plan-distribution-card.tsx#L32-L37src/features/system/components/plan-donut-chart-loader.tsx#L12-L13
🤖 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/app/`(system)/system/(dashboard)/page.tsx around lines 46 - 49, Remove
the ⚠️ emoji from the comments at src/app/(system)/system/(dashboard)/page.tsx
lines 46-49, src/features/system/components/summary-card.tsx lines 17-19,
src/features/system/components/plan-distribution-card.tsx lines 6-8, 18-22, and
32-37, and src/features/system/components/plan-donut-chart-loader.tsx lines
12-13; replace it with plain-text warning wording or delete the comments while
preserving the surrounding implementation.
Source: Coding guidelines
recharts는 조각을 담는 `<g>`(recharts-zIndex-layer_*)와 툴팁 상자에 tabindex="-1"을 달아 둔다. 마우스로 누르면 그 `<g>`가 초점을 받아 브라우저가 자기 bbox에 기본 테두리를 그렸다 — 막대차트는 막대 영역이, 도넛은 도넛이 통째로 네모로 감싸여 고르지도 않은 게 선택된 것처럼 보였다. 앞서 `.recharts-surface`에 걸었던 건 초점을 받는 자리가 아니라 안 먹었다. tabindex="-1"은 애초에 키보드로 갈 수 없는 자리라 테두리를 지워도 잃는 게 없고, 키보드 초점은 차트 표면(tabindex="0")이 받으므로 링은 거기 :focus-visible에만 남긴다. 차트를 새로 놓을 때마다 같은 버그를 다시 만나므로 globals.css에 전역으로 둔다.
요금제는 유료 하나뿐인데(CLAUDE.md §요금제) 운영자 화면이 아직 Free를 보여줬다. 대시보드는 "62개사 중 24개사가 무료"라고 말했고, 구독 금액도 `plan === TEAM`이 아니면 0원으로 쳤다 — 운영자가 잘못된 회사 현황을 보고 판단하게 된다. PR #75가 범위 밖으로 미뤄 둔 항목이다. - 상수: `PLAN`·`Plan`·`PLAN_LABEL`을 지운다. 값을 남겨 두면 화면이 "어느 플랜이냐"를 다시 묻기 시작하고, 그 순간 없는 무료 요금제가 되살아난다. - 플랜 분포 도넛: 플랜이 하나면 "분포"라는 개념이 없다 — 카드·차트·로더를 지운다. - 구독 금액: 플랜으로 가르지 않는다. 모든 기업이 결제 대상이라 0원짜리 구독은 없다. 테스트도 "0원도 결제일 없는 건도 없다"로 바꿔 되살아나면 빨개진다. - MRR 보조문구 `Team N개사` → `월 반복 매출`. 자리를 채우려고 없던 지표를 지어내지 않는다. 빈 곁 컬럼에는 **이미 있는 값**을 옮겨 왔다(처리 큐 — 시스템 모니터링과 같은 소스). 대시보드의 나머지가 전부 기업·매출 축이라 시스템이 지금 건강한지는 아무 데도 없었다.
- 숫자를 `items-baseline`으로 맞추고 있었다. 왼쪽이 라벨+보조 **두 줄**이라 첫 줄 베이스라인에 붙이면 숫자만 위로 떠 보인다 — `items-center`로 바꾼다. - 목록을 가운데로 모으고 있었다(`justify-center`). 첫 줄이 옆 차트 카드 내용보다 한참 내려가 두 카드가 서로 다른 높이에서 시작하는 것처럼 보였다 — `justify-between`으로 고르게 벌린다. 브라우저에서 재서 확인했다: 두 카드 높이 314px로 같고, 세 줄 모두 높이 36px에 간격 35~36px, 좌우 열(1182·1380)도 어긋나지 않는다.
토끼 지적 반영. - `h-9`를 `<tr>`에 걸고 있었는데, `TableHead`가 `<th>`마다 기본 `h-10`을 얹어서 (`components/ui/table.tsx`) 머리 높이가 40px로 남아 있었다. 행 높이를 정하는 건 셀이라, 클래스만 보고는 눈치채기 어렵다 — 셀에 걸고 상수(`HEAD_CELL_CLASS`)로 묶는다. 브라우저에서 재서 확인: 머리 36px, 본문 48px. - 열 패딩 주석이 실제와 달랐다. "이름 열만 px-6"이라 적혀 있었지만 가입일 열도 px-6이다 — 양 끝 열이 px-6인 이유(카드 제목 px-7과 끝을 맞춘다)를 적는다. 토끼가 짚은 `outline-none`+`outline-2` 문제(Tailwind v4에서 `outline-none`이 `--tw-outline-style: none`이라 :focus-visible 링이 안 뜬다)는 이미 앞 커밋에서 해결됐다 — 유틸리티를 걷어내고 globals.css에 `outline: 2px solid`로 명시했다.
같은 화면을 두 PR이 나눠 갖고 있어 곁 컬럼을 디자인 쪽에서 다듬을 수가 없었다. 충돌은 전부 '삭제 채택'이다 — 플랜 분포 도넛 3파일은 #238이 지운 것이고, 그 자리는 처리 큐 카드가 대신한다.
- **곁 카드가 제 칸보다 100px 좁았다.** 처리 큐 카드가 `lg:w-64`(256px)로 폭을 또 정하고 있어 격자(360px)와 싸웠다 — 위아래 카드는 오른쪽 끝이 1480인데 이 카드만 1380에서 끝나, 중간 줄만 안으로 들어가 보였다. 폭은 격자가 정하게 둔다. (재서 확인: 세 블록 모두 오른쪽 끝 1480, 카드 폭 360) - **요약 카드 보조 문구가 흐렸다.** `text-muted-foreground/70`이라 흰 카드 위에서 대비가 2.7:1이었다 — 11px 본문은 4.5:1이 필요하다(WCAG). 알파를 걷어내 4.8:1로 올리고 12px로 키운다. 흐리게 안 만들어도 큰 숫자와 충분히 갈린다. - **표 머리 줄이 안 보였다.** 라이트에서 `--secondary`(#fafaf9)가 카드 흰색과 거의 같아 `/50`까지 씌우면 대비가 1.02:1이다. 띠는 두되(다크에선 보인다) 아래 보더를 더해 어느 테마에서도 머리와 본문이 갈리게 한다. - 처리 큐 숫자 17→20px. 곁 카드에서 제일 먼저 읽혀야 하는 값이다.
밑줄만 그어 둔 회색 글자 한 줄이라 카드 마감이 안 된 것처럼 보였다. - 칸 전체를 누를 자리로 만든다(보더도 카드 폭까지). 읽는 줄이 아니라 가는 줄이라는 게 모양으로 드러난다. - 오른쪽에 방향 표시(`ChevronRight`)를 두고, hover에 바탕과 화살표 이동을 얹는다(150ms). - 아이콘 옆 한글은 1px 내린다 — 라틴 기준으로 맞춘 아이콘과 시각 중심이 어긋난다.
`bg-secondary/50`을 깔고 있었는데, 라이트에서 `--secondary`(#fafaf9)가 카드 흰색과 거의 같아 대비가 1.02:1이었다 — 머리 줄이 있는지도 안 보였다. `--muted`·`--accent`도 같은 값이라 토큰을 바꿔 봐야 소용이 없다. 먹색을 6%로 옅게 깐다. 한 클래스로 두 테마가 다 된다 — 라이트에선 흰 바탕이 살짝 어두워지고(1.13:1), 다크에선 `--foreground`가 밝은 색이라 카드보다 밝아진다. 6%인 건 행 hover가 4%라 그보다 옅으면 마우스 얹은 행이 머리보다 진해지기 때문이다. **표 5개가 전부 같은 상태였다.** 대시보드만 고치면 /system 안에서 표마다 달라 보이므로 함께 맞추고, 네 곳에 흩어져 있던 문자열을 `table-style.ts` 한 곳으로 모은다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/features/system/components/pipeline-queue-card.tsx`:
- Line 20: Declare a named PipelineQueueCardProps interface containing the
queue: PipelineQueueSummary prop, then update PipelineQueueCard to use that
interface instead of the inline props object type.
In `@src/features/system/mock/billing.ts`:
- Around line 6-11: Derive the mock billing UNIT_PRICE in
src/features/system/mock/billing.ts lines 6-11 from the canonical BillingConfig
value defined in billing/types.ts instead of hardcoding 9_900. Update the
expected amount calculation in src/features/system/mock/billing.test.ts lines
37-42 to use that same BillingConfig value, ensuring both implementation and
test remain synchronized with configuration changes.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 040a18e8-4000-46c0-af97-70c540b31871
📒 Files selected for processing (18)
src/app/(system)/system/(dashboard)/page.tsxsrc/constants/system.tssrc/features/system/components/approval-table.tsxsrc/features/system/components/company-table.tsxsrc/features/system/components/failed-pipeline-table.tsxsrc/features/system/components/pipeline-queue-card.tsxsrc/features/system/components/plan-distribution-card.tsxsrc/features/system/components/plan-donut-chart-loader.tsxsrc/features/system/components/plan-donut-chart.tsxsrc/features/system/components/recent-companies-table.tsxsrc/features/system/components/subscription-table.tsxsrc/features/system/components/summary-card.tsxsrc/features/system/mock/billing.test.tssrc/features/system/mock/billing.tssrc/features/system/mock/companies.tssrc/features/system/mock/dashboard.tssrc/features/system/table-style.tssrc/features/system/types.ts
💤 Files with no reviewable changes (3)
- src/features/system/components/plan-distribution-card.tsx
- src/features/system/components/plan-donut-chart-loader.tsx
- src/features/system/components/plan-donut-chart.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/features/system/components/summary-card.tsx
- src/features/system/components/recent-companies-table.tsx
| /** | ||
| * 1인당 월 단가 — `features/billing/plans.ts`의 시안값과 맞춘다(₩9,900). | ||
| * ⚠️ **목이다.** 금액의 정본은 BE가 주는 `BillingConfig`이고, 좌석 과금 여부도 확정 전이다 | ||
| * (CLAUDE.md §요금제). 확정되면 여기가 아니라 그 값을 읽는다. | ||
| */ | ||
| const UNIT_PRICE = 9_900; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
요금 단가의 단일 정본을 사용하세요.
목 구현과 테스트가 모두 9_900을 독립적으로 고정합니다. BillingConfig가 변경되면 표시 값과 테스트 기대값이 함께 오래된 값으로 남습니다.
src/features/system/mock/billing.ts#L6-L11:UNIT_PRICE를BillingConfig의 단가에서 파생하세요.src/features/system/mock/billing.test.ts#L37-L42: 기대 금액을 같은BillingConfig값으로 계산하세요.
As per coding guidelines, “derive billing, pricing, usage, and plan data from billing/types.ts BillingConfig.”
📍 Affects 2 files
src/features/system/mock/billing.ts#L6-L11(this comment)src/features/system/mock/billing.test.ts#L37-L42
🤖 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/features/system/mock/billing.ts` around lines 6 - 11, Derive the mock
billing UNIT_PRICE in src/features/system/mock/billing.ts lines 6-11 from the
canonical BillingConfig value defined in billing/types.ts instead of hardcoding
9_900. Update the expected amount calculation in
src/features/system/mock/billing.test.ts lines 37-42 to use that same
BillingConfig value, ensuring both implementation and test remain synchronized
with configuration changes.
Source: Coding guidelines
토끼 지적 반영 — 인라인 객체 타입으로 두고 있었다(CLAUDE.md §폴더·네이밍: "props 인터페이스 명시").
**제목 표식.** 카드마다 똑같은 먹색 점이라 아무것도 안 알렸고, 화면이 제목만 여덟 줄 늘어선 것처럼 읽혔다. 카드 주제를 가리키는 아이콘으로 바꾼다 — 색을 더하지 않고도(§디자인 토큰: 색으로 알리는 건 에러뿐) 카드가 서로 구분된다. 여덟 곳에 복사돼 있던 제목 규격도 `SystemCardHeading` 한 자리로 모은다. 한 곳이 어긋나면 카드마다 제목 높이가 달라지던 자리다. **숫자와 단위.** `"3건"`이 한 덩이라 `건`까지 30px로 커져 숫자와 같은 무게로 읽혔다 — 먼저 읽혀야 하는 건 숫자다. 갈라서 단위를 작고 흐리게 붙인다 (요약 카드·처리 큐).⚠️ 없던 단위를 만들지는 않는다 — `62`·`₩8.4M`은 그대로다. 아이콘 옆 한글은 1px 내린다(라틴 기준 아이콘과 시각 중심이 어긋난다). 대시보드·구독·매출·시스템 모니터링 세 화면이 같은 컴포넌트를 쓰므로 함께 바뀐다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/app/`(system)/system/billing/page.tsx:
- Around line 30-40: Replace the hardcoded "건" unit labels in the billing
summary entries within src/app/(system)/system/billing/page.tsx lines 30-40 with
the shared localized label source. Apply the same change to the waiting,
processing, and failed entries in src/app/(system)/system/monitor/page.tsx lines
20-27, using a shared constants/ label map defined with as const and English
keys rather than page-local Korean strings.
In `@src/features/system/components/system-card-heading.tsx`:
- Around line 13-18: Remove all ⚠️ emoji characters from the documentation
comments in system-card-heading.tsx, preserving the surrounding Korean text and
formatting unchanged.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3fc03d58-5014-4f47-a04c-96a1b5aa8fb7
📒 Files selected for processing (13)
src/app/(system)/system/(dashboard)/page.tsxsrc/app/(system)/system/billing/page.tsxsrc/app/(system)/system/monitor/page.tsxsrc/features/system/components/failed-pipeline-table.tsxsrc/features/system/components/mrr-chart-card.tsxsrc/features/system/components/notice-compose-card.tsxsrc/features/system/components/notice-history-card.tsxsrc/features/system/components/pipeline-queue-card.tsxsrc/features/system/components/recent-companies-table.tsxsrc/features/system/components/signup-chart-card.tsxsrc/features/system/components/stage-timing-card.tsxsrc/features/system/components/summary-card.tsxsrc/features/system/components/system-card-heading.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
- src/features/system/components/pipeline-queue-card.tsx
- src/features/system/components/signup-chart-card.tsx
- src/features/system/components/recent-companies-table.tsx
- src/app/(system)/system/(dashboard)/page.tsx
- src/features/system/components/summary-card.tsx
| value: `${summary.paidCount}`, | ||
| unit: "건", | ||
| meta: formatCompactKrw(summary.paidAmount), | ||
| }, | ||
| { label: "미납", value: `${summary.unpaidCount}건`, meta: "안내 발송 필요" }, | ||
| { label: "해지", value: `${summary.canceledCountThisMonth}건`, meta: "이번 달" }, | ||
| { label: "미납", value: `${summary.unpaidCount}`, unit: "건", meta: "안내 발송 필요" }, | ||
| { | ||
| label: "해지", | ||
| value: `${summary.canceledCountThisMonth}`, | ||
| unit: "건", | ||
| meta: "이번 달", | ||
| }, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
공통 단위 라벨을 사용하세요.
두 페이지가 "건"을 직접 정의합니다. 공통 번역 키 또는 constants/의 라벨 맵을 사용하면 다국어 처리와 라벨 변경을 한 곳에서 관리할 수 있습니다.
src/app/(system)/system/billing/page.tsx#L30-L40: 결제 완료·미납·해지 항목의unit을 공통 라벨 소스에서 가져오세요.src/app/(system)/system/monitor/page.tsx#L20-L27: 대기·처리 중·실패 항목의unit을 동일한 공통 라벨 소스에서 가져오세요.
As per coding guidelines: “Define domain value lists in constants/ using as const and label maps; use English constants in code and localized labels in the UI without hardcoded labels.”
📍 Affects 2 files
src/app/(system)/system/billing/page.tsx#L30-L40(this comment)src/app/(system)/system/monitor/page.tsx#L20-L27
🤖 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/app/`(system)/system/billing/page.tsx around lines 30 - 40, Replace the
hardcoded "건" unit labels in the billing summary entries within
src/app/(system)/system/billing/page.tsx lines 30-40 with the shared localized
label source. Apply the same change to the waiting, processing, and failed
entries in src/app/(system)/system/monitor/page.tsx lines 20-27, using a shared
constants/ label map defined with as const and English keys rather than
page-local Korean strings.
Source: Coding guidelines
| * ⚠️ **여덟 곳에 같은 문자열이 복사돼 있었다.** 규격(`px-7 pt-6 pb-3`·17px)이 한 벌이라 | ||
| * 한 곳이 어긋나면 카드마다 제목 높이가 달라진다 — 한 자리로 모은다. | ||
| * ⚠️ 표식이 **먹색 점**이었다. 카드마다 같은 점이라 아무것도 안 알리고, 화면 전체가 | ||
| * 제목만 여덟 줄 늘어선 것처럼 읽혔다. 카드 주제를 가리키는 아이콘으로 바꾼다 — | ||
| * 색을 더하지 않고도(§디자인 토큰: 색으로 알리는 건 에러뿐) 카드가 구분된다. | ||
| * ⚠️ 아이콘 옆 한글은 **1px 내린다** — 라틴 글자 기준으로 맞춘 아이콘과 한글의 시각 중심이 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
주석의 이모지 문자를 제거하세요.
문서 주석에 ⚠️ 문자가 세 번 있습니다. 해당 문자를 삭제하고 텍스트만 유지하세요.
As per coding guidelines, src/**/*.{tsx,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/features/system/components/system-card-heading.tsx` around lines 13 - 18,
Remove all ⚠️ emoji characters from the documentation comments in
system-card-heading.tsx, preserving the surrounding Korean text and formatting
unchanged.
Source: Coding guidelines
하루 종일 큐·실패 목록을 보는 운영 화면이라 밝은 바탕이 오래 보기 힘들고,
사용자 화면과 섞이지 않는다는 신호도 된다.
**바꿀 수는 있다.** 상단바 토글은 그대로 살아 있고, 그 선택은 `/system` 셸 안에서만
산다 — 운영자 화면을 밝게 봤다고 사용자 화면까지 밝아지면 안 된다.
- `.dark`를 `<html>`이 아니라 셸 상자에 붙인다. globals.css의 다크 토큰이 클래스
선택자(`.dark {}`)라 상자에 붙이면 그 안쪽만 뒤집히고, `dark:` 유틸리티도
`&:is(.dark *)`라 함께 따라온다.
- next-themes를 또 얹지 않는다 — 그건 `<html>`에 쓰므로 둘이 같은 자리를 두고 싸운다.
- localStorage가 아니라 **쿠키**다. 클래스가 React가 든 상자에 붙어서 첫 페인트 전에
돌릴 스크립트를 걸 자리가 없다 — 서버가 쿠키를 읽어 처음부터 맞는 밝기로 그려 보낸다.
실제로 light로 두고 새로고침해도 번쩍이지 않는 것을 확인했다.
- `ThemeToggle`은 범위 테마가 있으면 그쪽을, 없으면 지금까지처럼 전역을 바꾼다.
⚠️ `text-foreground`를 셸에서 다시 잡는다. `var()`는 값을 쓴 요소에서 풀리는데
그 클래스가 `<body>`(범위 밖)에 걸려 있어 라이트 먹색으로 이미 계산된다 —
상자에 `.dark`만 붙였더니 제목과 큰 숫자가 통째로 안 보였다.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/app/(system)/layout.tsx (1)
27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win명시적 props interface를 선언하세요.
SystemLayout의 인라인 객체 타입을SystemLayoutPropsinterface로 분리하세요.수정 예시
+interface SystemLayoutProps { + children: ReactNode; +} + -export default async function SystemLayout({ children }: { children: ReactNode }) { +export default async function SystemLayout({ children }: SystemLayoutProps) {As per coding guidelines,
**/*.{ts,tsx}에는 명시적 props interface가 필요합니다.🤖 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/app/`(system)/layout.tsx at line 27, SystemLayout의 인라인 props 타입을 제거하고, children: ReactNode를 포함하는 명시적 SystemLayoutProps interface를 선언한 뒤 함수 시그니처에서 해당 interface를 사용하세요.Source: Coding guidelines
src/components/common/scoped-theme.tsx (1)
24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win불리언 props 이름을
is접두사로 통일하세요.
initialDark는 불리언인데is/has/should접두사를 사용하지 않습니다.isInitialThemeDark와 같이 이름을 바꾸고 계약 전체를 함께 갱신하세요.
src/components/common/scoped-theme.tsx#L24-L24:ScopedThemeProviderProps의initialDark를isInitialThemeDark로 변경하세요.src/components/common/scoped-theme.tsx#L45-L51: 구조 분해와useState초기값 참조를 새 props 이름으로 변경하세요.src/app/(system)/layout.tsx#L34-L34:ScopedThemeProvider호출의 props 이름을 새 계약에 맞게 변경하세요.As per coding guidelines, 불리언 이름은
is/has/should접두사를 사용해야 합니다.🤖 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/common/scoped-theme.tsx` at line 24, Rename the boolean prop from initialDark to isInitialThemeDark across the full ScopedThemeProvider contract. Update ScopedThemeProviderProps and the destructuring/useState initialization in src/components/common/scoped-theme.tsx (lines 24 and 45-51), then update the ScopedThemeProvider call in src/app/(system)/layout.tsx (line 34) to use the new prop name.Source: Coding guidelines
🤖 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/scoped-theme.tsx`:
- Around line 53-60: Update the toggle callback around setIsDark so the state
updater only computes and returns the next theme value, without writing
document.cookie. Move the cookie write into the outer toggle flow using the
resulting theme value, while preserving the existing cookieName dependency and
one-year persistence settings.
---
Nitpick comments:
In `@src/app/`(system)/layout.tsx:
- Line 27: SystemLayout의 인라인 props 타입을 제거하고, children: ReactNode를 포함하는 명시적
SystemLayoutProps interface를 선언한 뒤 함수 시그니처에서 해당 interface를 사용하세요.
In `@src/components/common/scoped-theme.tsx`:
- Line 24: Rename the boolean prop from initialDark to isInitialThemeDark across
the full ScopedThemeProvider contract. Update ScopedThemeProviderProps and the
destructuring/useState initialization in src/components/common/scoped-theme.tsx
(lines 24 and 45-51), then update the ScopedThemeProvider call in
src/app/(system)/layout.tsx (line 34) to use the new prop name.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2a35d7d0-1f38-45f3-830e-14f23f33b62f
📒 Files selected for processing (4)
src/app/(system)/layout.tsxsrc/components/common/scoped-theme.tsxsrc/components/common/theme-toggle.tsxsrc/features/system/theme.ts
다크가 기본이 되면서 무채색 표식이 바탕에 묻혔다. 차트 막대와 **같은 액센트** (`--chart-1`)로 칠해 운영자 화면 전체가 한 벌로 읽히게 한다 — 여기만 다른 색을 쓰면 액센트가 두 개가 된다. `--primary`(파랑)는 이 제품에서 안 쓰기로 한 색이다.⚠️ 글자는 안 칠한다. 색으로 알리는 건 에러(빨강)뿐이라(CLAUDE.md §디자인 토큰) 제목까지 물들이면 카드마다 뭔가 알리는 것처럼 읽힌다 — 표식은 알림이 아니라 표식이다. 대비는 두 테마 모두 3:1 위다(다크 3.4:1 · 라이트 3.2:1, 그래픽 기준).
"아이콘 옆 한글은 1px 내린다"를 그대로 적용했더니 **오히려 글자가 아이콘보다 아래로** 갔다. 브라우저에서 잉크 중심으로 실측한 값이다. - 카드 제목(17px·leading-7·items-center): 내렸을 때 +0.78 → 안 내리면 -0.22 - 카드 바닥 링크(12px): 내렸을 때 +0.73 → 안 내리면 -0.40 `items-center`가 이미 줄 상자를 맞춰 주고 있어서 추가 보정이 필요 없었다. 크기·줄높이 조합이 바뀌면 다시 재야 한다 — 저 1px은 어느 조합에서 나온 값이다.
"아이콘 옆 한글은 1px 내린다"가 레포 전체에 58군데 퍼져 있었는데, 재 보니 **보정이 없을 때가 더 정확했다.** `items-center`가 이미 줄 상자를 맞춰 주고 있어서 1px을 더하면 글자가 아이콘보다 아래로 내려간다. 브라우저에서 글자 잉크 중심과 아이콘 중심을 재서 확인한 값(양수 = 글자가 아래): | 자리 | 전 | 후 | | --- | --- | --- | | 사이드바 항목(13px) | +0.73 | -0.27 | | 상단바 제목(22px, 1.5px 내림) | +0.94 | -0.56 | | 카드 제목(17px) | +0.78 | -0.22 | 31개 파일 58건. 남은 `translate-y-px`는 버튼 눌림 효과 (`active:not-aria-[haspopup]:translate-y-px`) 하나뿐이다 — 그건 정렬이 아니다. 확인: `/system`(다크·라이트) · 랜딩 · 로그인 · 요금제에서 아이콘+글자 쌍을 훑어 1px을 넘는 어긋남이 없다(요금제 16쌍 전부 0.4px 안쪽).⚠️ 별도 이슈를 안 만들고 이 브랜치에 담는다(요청). 시스템 대시보드 정비와 주제가 다르지만, 같은 화면을 보다 발견한 것이라 여기서 끝낸다.
토끼 지적 반영. `setIsDark` 업데이터 안에서 `document.cookie`를 쓰고 있었다 — 업데이터는 순수해야 하고 Strict Mode는 개발 중에 일부러 두 번 부른다. 값이 같아 눈에 띄진 않았지만 React가 한쪽 호출을 버리는 구조라 기대면 안 되는 자리다. 부수 효과는 이벤트 핸들러에서 한다. 두 번 눌러 확인: dark → light(쿠키 light) → dark(쿠키 dark).
`TableRow`가 기본으로 `hover:bg-muted/50`을 걸어서 머리 줄에서는 그걸 지워야 하는데, `hover:bg-transparent`로 지우고 있었다 — 마우스를 얹는 순간 방금 깐 띠까지 통째로 없어졌다. 머리 줄은 누르는 자리가 아니라 반응할 이유가 없으므로, 같은 값을 다시 줘서 아무 일도 일어나지 않게 한다. 띠가 안 보이던 때(`bg-secondary/50`, 대비 1.02:1)는 이 버그도 같이 안 보였다 — 표 5개가 전부 이 상태였다.
관리자 화면 일곱 개를 재 보니 표만 카드 안쪽 여백과 다른 선에서 시작했다. 시스템 카드는 제목·본문이 전부 28px(`px-7`) 안쪽인데 표는 16~24px이었다. - 실패 목록: 제목 281 / 표 269 — **12px** 어긋남 - 최근 가입 기업: 제목 281 / 표 277 — **4px** 어긋남 (여기엔 "카드 제목과 맞추려고 `px-6`을 쓴다"는 주석까지 달려 있었는데 24 ≠ 28이었다) 바깥 열을 `px-7`·`pl-7`·`pr-7`로 올려 다섯 표를 전부 같은 선에 세운다. 가운데 열은 `px-4`로 둔다 — 거기까지 28px을 주면 열 사이가 벌어져 한 줄로 안 읽힌다. 승인 상세 카드도 제목 줄만 먹색 점을 직접 그리고 있어 `SystemCardHeading`으로 바꾼다. 나머지가 아이콘으로 바뀐 뒤 이 화면만 튀었다. 카드에 `p-7`을 통으로 주던 것을 제목·본문이 각자 여백을 갖게 나눴다. 확인(다크·1512px): 일곱 화면 전부 카드 좌우 끝 252/1248 일치, 표 첫 열과 제목 표식 모두 281, 가운데 정렬 열의 머리글·본문 중심 차이 0, 아이콘+글자 쌍 1px 초과 없음.
**구독 목록의 이동 링크가 카드 밖에 떠 있었다.** 카드 아래 허공에 오른쪽 정렬로 붙어 있어 어느 카드에 딸린 것인지 모양으로 알 수 없었고, 모서리 밖으로 삐져나와 마감이 안 된 것처럼 보였다. 제목 줄 오른쪽으로 옮긴다 — `SystemCardHeading`에 `action` 자리를 만들고, 어디로 보낼지는 화면이 정하므로 화면이 넘긴다. 표에 제목(`구독 목록`)이 생겨 대시보드·모니터링 카드와 모양이 같아진다. **기업 관리의 머리 줄이 두 개였다.** 필터가 위에 한 줄, `전체 N건`이 아래 또 한 줄. 필터는 오른쪽 끝에만 붙어 있어 그 줄 왼쪽 절반이 통째로 비었고 건수는 혼자 떠 있었다. 한 줄로 합친다(건수 왼쪽 · 필터 오른쪽).⚠️ 합치면서 필터 바가 두 줄로 깨졌다 — 검색칸이 `w-full sm:ml-auto`로 스스로 폭을 끝까지 밀고 있었기 때문이다. 혼자 한 줄을 쓰던 시절의 밀기라 걷어냈다.
**줄의 주인공이 없었다.** 기업명이 나머지 값과 같은 크기·같은 흐린 회색이라 무엇을 보는 표인지 드러나지 않았다 — 첫 열(기업명·회사명·회의 ID)을 먹색 `font-medium`으로 세운다. 나머지는 흐린 회색 그대로라 눈이 이름 → 값 순서로 간다. **값이 전부 가운데 정렬이라 세로선이 안 생겼다.** 자릿수가 제각각인 숫자·날짜 (`8명`·`130명`, `₩79,200`·`₩435,600`)를 가운데에 두면 줄마다 좌우로 흔들려 "떠 있는 섬"이 여러 개로 읽힌다 — 오른쪽 정렬로 레일을 만든다(DESIGN §3). 글자 열(대표자·이메일·오류·기업 코드)은 가운데를 유지한다. 거긴 흔들릴 자릿수가 없다. **액션·상태를 오른쪽 끝에 붙인다.** 구독 목록은 액션 칸이 18%라 버튼 왼쪽에 170px이 비어 있었다 — 폭을 다시 나누고(26/10/16/20/14/14) 오른쪽 끝에 세워, 표가 카드 오른쪽 안쪽 여백에서 끝난다. 구독 목록 본문도 12 → 13px로 올린다(다른 표와 같은 규격, DESIGN §4). 행 높이 42 → 48.
제목 아래가 12px(`pb-3`)뿐이라 표 머리 띠·막대 첫 줄이 제목에 달라붙었다 — 제목이 카드의 머리가 아니라 본문 첫 줄처럼 읽혔다. `pt-6 pb-3` → `pt-5 pb-5`. 제목은 위로 4px 올라가고 본문과는 8px 더 떨어진다. 카드 높이는 4px만 는다. 재서 확인: 제목 글자 아래 → 첫 내용 15 → 23px. 차트 카드와 처리 큐 카드가 본문에 `pt-2`·`pt-1`을 덧대고 있어 걷어냈다 — 이제 제목 줄이 그 간격을 혼자 갖는다. 여덟 카드가 같은 값을 쓴다.
여백만으로는 제목이 안 섰다. 재 보니 제목 글자 위 24px · 아래 23px로 **거의 대칭**이라, 카드의 머리가 아니라 본문 첫 줄처럼 읽혔다 — 위아래를 벌려 봐도 마찬가지였다. 표 카드는 바로 아래 머리 띠가 또 있어서 제목과 경쟁하기까지 했다. 제목 줄에 아래 경계선을 둔다. "여기까지가 머리"가 모양으로 정해지면, 아래에 표 머리 띠가 붙든 차트가 붙든 제목이 흔들리지 않는다. - 선이 나누는 몫을 하므로 제목 아래 여백은 줄인다(`pb-5` → `pb-4`). - 선 밑 간격은 본문이 정한다 — 표는 머리 띠가 선에 바로 붙는 게 맞고, 차트·목록·폼은 자기 `pt-5`(처리 큐는 `pt-4`)를 갖는다. - `action`이 있는 카드도 선이 카드 폭 전체를 지난다.
전 화면 정렬 점검 중 발견. 앞서 쓸어낸 `translate-y-px`와 같은 종류의 손보정이 `mt-[1px]`로 남아 있었다 — 상자(18px)와 라벨 줄 높이(18px)가 같아 `items-start`면 이미 위가 맞는데, 1px을 더해 글자보다 아래로 처져 있었다. 재서 확인: 라벨 잉크 중심 기준 -1.4px → -0.4px.⚠️ `items-center`가 아니라 `items-start`인 건 그대로 둔다 — 약관 문구가 두 줄로 넘칠 때 상자가 가운데로 내려가면 안 된다.
아이콘을 글자 옆에 그냥 두니 **글머리 기호처럼** 읽혔다 — 16px 아이콘과 17px 글자가 크기가 비슷해 같은 줄의 한 글자로 보였다. 28px 상자에 담아 크기와 무게를 주면 글자와 다른 층이 되어, 제목을 가리키는 표식이라는 게 드러난다. - 상자 바탕은 액센트를 아주 옅게(10%) 깐다. 아이콘만 색이면 점 하나가 떠 있는 것 같은데, 상자까지 같은 색조면 표식 하나로 뭉쳐 보인다. 옅어서 색으로 뭘 알리지도 않는다(알리는 건 에러(빨강)뿐). - 상자가 28px이라 제목 줄이 그만큼 두꺼워진다 — 위 여백을 20 → 16px로 줄여 카드 높이는 그대로 둔다. 글자와 표식 간격은 8 → 10px. 라이트·다크 둘 다 확인. 관리자 화면 밖은 점(●) 제목 그대로다.
**가변 길이 글자를 가운데 두고 있었다.** 길이가 제각각인 값(기업명·오류 문구)을 가운데로 두면 줄마다 좌우로 밀려 세로선이 안 생긴다 — 실측으로 `기업` 8.5px, `오류` 9.7px씩 흔들렸다. 왼쪽으로 붙이니 흔들림 0이 된다. 오른쪽 정렬은 **자릿수가 실제로 다른 값** (숫자·금액·날짜)과 뱃지·버튼에만 쓴다. 규칙은 `table-style.ts`에 적어 둔다.⚠️ 앞 커밋에서 **머리만 바꾸고 본문 셀을 놓친 열이 있었다** — 기업 관리의 구성원·이번달 회의·가입일은 머리가 오른쪽인데 값은 가운데였다. 다섯 표 전부 머리·본문 짝을 맞췄다. **열 폭도 눈대중이라 어느 열은 내용이 칸을 꽉 채웠다.** 그러면 옆 열과 붙는다 — 열 사이가 승인 16·53·108·157·97px, 기업 관리 16·193·84·90·43px, 실패 목록 123·101·142·**16**·83px로 제각각이었다. 가장 긴 내용을 재서 남는 폭을 고르게 얹어 다시 나눴다(실패 목록 74~123px로 균일). `시각`도 왼쪽으로 옮겼다. `2025-07-21 14:32`은 늘 같은 폭이라 왼쪽에 붙여도 안 흔들리고, 오른쪽에 두면 그 열만 제 칸 끝으로 밀려 옆 열과 16px까지 붙었다.
머리글은 **글자**를 오른쪽 끝에 맞추는데 버튼·뱃지는 **상자**를 맞춘다. 그러면 정작 글자끼리는 상자 안쪽 여백만큼 어긋난다 — 실측으로 `액션` 머리글이 버튼 글자보다 9px 오른쪽에 있었다. 겉보기 정렬은 맞는데 눈에는 안 맞아 보이던 원인이다. 머리글을 그만큼 더 밀어 글자끼리 세로선을 맞춘다. 값은 상자 규격에서 온다 — 버튼 = 안쪽 8px + 테두리 1px = 9px, 뱃지 = 안쪽 8px. 열 여백(마지막 열 28px, 가운데 열 8px)을 더해 `table-style.ts`에 상수로 둔다. 다섯 표 28개 열을 **글자 기준**으로 다시 감사했다 — 머리글·값 차이와 값 흔들림이 전부 0이다(대시보드 4 · 승인 6 · 기업 관리 6 · 구독 목록 6 · 실패 목록 6).
**"정렬"을 계속 잘못 읽고 있었다.** 머리글과 값의 **양끝**(왼쪽/오른쪽)을 맞추고 있었는데, 원하는 건 머리글이 값의 **가운데**에 서는 것이었다. 양끝을 맞추면 머리글이 값 한쪽 끝에 붙어 서서 어느 값을 가리키는지 눈으로 안 잡힌다. 첫 열(그 줄의 주인공)만 왼쪽이고, **나머지 열은 머리글·값 둘 다 가운데**다. 다섯 표 전부 같은 규칙으로 맞췄다. 작업 중 걸러낸 것: - 정렬 클래스가 아예 없어 기본값(왼쪽)으로 남아 있던 머리글 여러 개 - 여러 줄로 쓰여 한 번에 못 바꾼 셀(`오류`·`기업`) — 54px까지 어긋나 있었다 - 마지막 열은 셀에만 `pr-7`이 있어 중심이 10px 밀렸다 — 머리글에도 같은 여백을 준다 - 버튼·뱃지는 상자가 아니라 **중심**을 맞추면 안쪽 여백 문제가 아예 없어진다 (양끝 맞춤 때 9px 어긋나던 그 문제) 중심 기준으로 다시 감사: 대시보드·기업 승인·기업 관리·구독 목록·시스템 모니터링의 가운데 정렬 열 22개 전부 어긋남 0.
**담당자 이메일만 왼쪽에 남아 있었다.** 여러 줄로 쓰인 `TableCell`이라 앞선 두 번의 일괄 수정에서 계속 빠졌다 — 실측 `text-align: start`, 머리글과 최대 50px 어긋남. 여러 줄 형태까지 훑어 마지막 하나를 잡았다. **열 사이 간격이 제각각이었다.** 기업 관리에서 44·75·76·72·71px — `기업명` 뒤가 오히려 제일 좁았다. 그 줄의 주인공이라 뒤를 넉넉히 떼어야 이름이 먼저 읽히는데 반대였다. 내용폭·안쪽 여백을 브라우저에서 재서 폭을 다시 풀었다(39/17/9/11/13/11%). 주인공 뒤는 넓게, 나머지 네 간격은 65·56·57·56px로 편차 9px 안에 들어온다.
관리자 화면의 변경 동작 아홉 개를 훑어 규칙(CLAUDE.md §렌더링·데이터: 결과는 토스트, 파괴적 작업 확인은 Dialog)에 안 맞는 두 곳을 채웠다. 둘 다 **공용** 컴포넌트를 쓴다. **① 기업 정지·정지 해제** — 폼 제출로 **바로 실행**됐고 결과 알림도 없었다. 그 회사 사람 전원이 워크스페이스에 못 들어오는 조작인데 눌렀는지도 모르게 끝났다. `ConfirmDialog`(정지는 파괴적 표시) + 성공·실패 토스트를 붙인다. **② 공지 발행** — 토스트는 있었지만 확인창이 없었다. 남의 회사로 나가고 되돌릴 수 없다. `ConfirmDialog`를 앞에 두고, 대상·되돌릴 수 없음을 문구에 적는다. 실패 토스트도 없어서 실패하면 아무 일도 없는 것처럼 보였다 — 같이 채운다.⚠️ **포털이 범위 테마 밖으로 새던 것도 같이 고쳤다.** Dialog·Sheet·Select는 기본으로 `<body>`에 그려져서, 다크로 보는 관리자 화면 위에 **흰 창**이 떴다(실제로 기업 상세 시트가 흰색이었다). 범위 테마의 상자를 `container`로 넘겨 그 안에 그린다 — 범위 밖이면 지금까지처럼 `<body>`다. 이미 갖춰져 있던 곳(고치지 않음): 승인·반려(확인창+토스트), 미납 안내 발송(메일 확인 Dialog+토스트), 파이프라인 재처리(토스트 — 되돌릴 수 있는 조작이라 확인창은 안 둔다).
**토스트가 두 개씩 쌓였다.** 목록이 `?done=`을 읽어 띄우는데, 이펙트는 한 번만 돈다는
보장이 없다 — 개발 모드(Strict Mode)는 일부러 두 번 돌리고 Fast Refresh·리마운트로도
다시 돈다. 그때마다 `done`이 URL에 그대로라(`router.replace`는 이펙트가 끝난 뒤에나
반영된다) 같은 토스트가 또 생겼다.
`toast(…, { id })`를 준다 — sonner가 같은 id는 새로 쌓지 않고 갱신하므로 리마운트까지
막힌다. ref 플래그도 같이 둬서 `replace`를 두 번 부르지 않는다.
**승인 상세 카드**는 2열 격자를 한 줄에 한 항목으로 바꿨다. 값 길이가 제각각이라
오른쪽 칸이 통째로 비었고(담당자 이메일 줄), 어느 라벨의 값인지 눈이 한 번 더 찾았다.
라벨 왼쪽·값 오른쪽에 줄 사이를 선으로 끊어 대장처럼 읽히게 한다.
- 주의 문구의 채운 상자를 걷어내고 한 줄로 — 짧은 문구인데 상자를 씌우니 값보다 무거웠고,
이제 확인창이 같은 말을 한 번 더 한다.
- 실행 줄은 선으로 끊어 붙인다.
왼쪽 라벨 줄에 서 있어서 **항목이 하나 더 있는 것처럼** 읽혔다. 이건 값이 아니라 버튼에 딸린 설명이라, 누를 자리 위에 둬야 무엇에 대한 경고인지가 자리로 드러난다.
**시트가 세로로 비었다.** 담는 값이 여섯 줄뿐인데 화면 높이를 다 쓰는 시트라 아래가 통째로 남았다 — 내용만큼만 차지하는 모달로 바꾼다. 값 줄은 승인 상세와 같은 규격 (라벨 왼쪽·값 오른쪽·줄 사이 선)이라 두 화면을 오갈 때 다시 읽지 않아도 된다. **모달 위에 모달을 띄우지 않는다.** 공용 `ConfirmDialog`를 겹쳤더니 카드가 두 겹이 되고 닫기 버튼도 두 개가 됐다 — 어느 것을 닫는지도 헷갈린다. 같은 모달 안에서 단계만 바꾼다(값 → 질문). 묻는 일은 그대로 한다. `ConfirmDialog`는 부모가 모달이 아닌 곳 (공지 발행·승인 상세)에서 계속 쓴다. **끝나면 닫는다.** 열어 두니 방금 정지한 것을 그 자리에서 바로 해제할 수 있어 정지 ↔ 해제를 무한히 오갈 수 있었다 — 확인창을 거친 의미가 옅어진다. 승인·반려도 끝나면 목록으로 돌아간다. 실패했을 때는 안 닫는다(무엇이 안 됐는지 볼 자리가 없어진다).⚠️ 실행 버튼을 `default`(파랑)에서 `ink`(먹색)로 바꾼다. 이 제품은 파랑을 강조색으로 안 쓴다(§디자인 토큰: 색으로 알리는 건 에러(빨강)뿐) — 승인 화면의 `승인`과 같은 색이다. 상태 뱃지도 파란 `Badge` 대신 표와 같은 `StatusBadge`를 쓴다.
겹치는 게 싫어서 확인 단계를 이 파일 안에 직접 그렸는데, 그러면 같은 물음을 화면마다 다시 만들게 된다 — 공용을 쓰는 이유가 사라진다. **겹치지 않으면서 공용을 쓰는 방법으로 바꾼다: 상세를 닫고 확인창을 띄운다.** 한 번에 하나만 뜨므로 카드가 두 겹이 되지도, 닫기 버튼이 두 개가 되지도 않는다. 회사 이름이 확인창 제목에 있어 맥락은 안 끊긴다. - 확인창은 상세 **밖**(형제)에 둔다 — 안에 두면 상세가 닫힐 때 같이 사라져 못 뜬다. - 취소하면 상세로 되돌아온다. 실수로 눌렀을 때 값 화면을 다시 찾지 않아도 된다. - 확인창 때문에 닫히는 것은 진짜로 닫는 게 아니므로 주소를 건드리지 않는다.⚠️ 승인 상세는 페이지 그대로 둔다(2026-08-06 팀 확정). 심사해야 하는 긴 흐름이라 목록 위에 띄우는 짧은 흐름과 성격이 다르다.
두 화면이 저마다 다르게 생겨서, 같은 서비스의 같은 일인데 열 때마다 새 화면처럼 읽혔다 — 공용 창(`ConfirmDialog`·`ResultDialog`)의 규격을 그대로 따른다. 원 표식 → 가운데 제목(20px) → 설명(13px) → 값 → 버튼, 폭 420, 안쪽 여백 32, 버튼 높이 44에 테두리. 이제 확인창·완료창·상세가 한 식구로 보인다. - `DialogMark`에 `none` 배지를 더한다. 상세는 묻지도 알리지도 않고 값만 보여주는데, 체크를 달면 "이미 끝났다", 느낌표를 달면 "잘못됐다"로 읽힌다. - 승인 상세의 두 버튼을 같은 폭·높이로 맞춘다. 한쪽이 넓으면 그쪽을 권하는 것처럼 보이고, 낮으면 같은 무게의 결정으로 안 읽힌다(§confirm-dialog와 같은 규칙). - 주의 문구도 공용 창의 에러 줄과 같은 자리·같은 규격(버튼 바로 위, 가운데, 13px)이다.⚠️ 승인 상세는 **페이지 그대로**다(2026-08-06 팀 확정) — 모양만 맞추고 구조는 안 바꿨다.
📋 PR 타입
🗂 작업 영역
🙋 관련 역할
📝 작업 내용
시스템 대시보드(
/system)를 정비했습니다. 표시 항목·조회 함수는 그대로이고 배치와 값만 손봤습니다.규칙이 갈리던 것
itemStyle·labelStyle로 고정했습니다--muted덩어리라 막대보다 눈에 먼저 들어왔습니다 → 4%로 눅였습니다클릭만 해도 테두리가 뜨던 것
recharts는 조각을 담는
<g>(recharts-zIndex-layer_*)와 툴팁 상자에tabindex="-1"을 달아 둡니다. 마우스로 누르면 그<g>가 초점을 받고, 브라우저가 자기 bbox에 기본 테두리를 그립니다 — 그래서 막대차트는 막대 영역이, 도넛은 도넛이 통째로 네모로 감싸여 고르지도 않은 게 선택된 것처럼 보였습니다.처음엔
.recharts-surface에 걸었는데 거긴 초점을 받는 자리가 아니라 안 먹었습니다(리뷰 중 스크린샷으로 확인).tabindex="-1"은 애초에 키보드로 갈 수 없는 자리라 테두리를 지워도 잃는 게 없고, 키보드 초점은 차트 표면(tabindex="0")이 받으므로 링은 거기:focus-visible에만 남겼습니다. 차트를 새로 놓을 때마다 같은 버그를 다시 만나므로globals.css에 전역으로 뒀습니다.브라우저에서 실제로 재현·검증했습니다 — 12개
recharts-zIndex-layer_*가 초점 시outline: auto 5px를 그리던 것이 규칙 적용 후 전부none이 되고,Tab으로 차트에 들어가면 링(solid)이 그대로 뜹니다.허전하던 것 — 색이 아니라 밀도로 풀었습니다
p-7인데 숫자만 작아 위아래가 비어 있었습니다gap-3→gap-7로 리듬 통일items-start를 쓰지 않는다")pl-4라 카드 제목(px-7)과 왼쪽 끝이 안 맞았습니다 → 저장소·사원 관리와 같은 규격(본문 13px, 이름 열px-6)✅ 작업 체크리스트
기본
design/system-dashboard#239, basedevelop)Closes #에 이슈 번호 기입console· 주석 처리된 코드 제거Z 필수
품질
:focus-visible로 옮겼습니다. 무채색 조각은 **35%**여야 3:1을 넘어 그 값을 유지했습니다(25%·30%는 2.2~2.6:1로 미달)loading/error/empty3상태 — 기존 그대로, 스켈레톤 크기만 본문과 맞춤🔗 연관 이슈
Closes #238
Closes #239
📸 스크린샷 (선택)
💬 리뷰 포인트
30px·13px·24px), 생성된 CSS에bg-chart-1규칙이 들어간 것도 확인했습니다. 테두리 건은 위에 적은 대로 브라우저에서 직접 재현·검증했습니다./system이 액센트를 쓰는 근거 — 제품 화면은 "색으로 알리는 건 에러뿐"이지만(DESIGN §5) 여기는 운영자만 보는 다른 면이고 이미 자기 색 언어가 있습니다(사이드바·발행 버튼). 대신 액센트를 두 곳으로 묶어 뜻을 갖게 했습니다 — 주인공 계열과 돈 내는 조각.작업 중
YAxis width={32}를 넣었다가 뺐습니다 — 기존margin.left: -16이 recharts 기본 폭을 전제로 당겨 둔 값이라, 좁히면 두 자리 눈금(12)이 잘릴 수 있는데 눈으로 확인할 수가 없었습니다.📝 추가 메모
Free요금제 잔재는 이 PR에 없습니다 — #238로 따로 잡았습니다. 요금제는 유료 하나뿐인데 대시보드가 아직Free 24개사를 보여주고 구독 금액도 플랜으로 계산합니다. 그건 CSS가 아니라 타입·목·금액 계산을 건드려야 해서, 디자인 PR에 섞으면 리뷰가 안 됩니다(PR #75가 그때 미뤄 둔 항목이기도 합니다).814개 테스트·타입체크·린트·빌드 통과.
Summary by CodeRabbit
이 PR 하나로 갑니다 — #242는 닫았습니다
같은 화면을 두 PR이 나눠 갖고 있어 곁 컬럼을 디자인 쪽에서 다듬을 수가 없었습니다.
fix/system-drop-free-plan#238을 이 브랜치에 머지했고, Free 요금제 제거(#238)와 대시보드 디자인 정비(#239)가 모두 여기 들어 있습니다.추가 정비 (곁 컬럼을 합친 뒤)
lg:w-64(256px)로 폭을 또 정하고 있어 격자(360px)와 싸웠습니다 — 위아래 카드는 오른쪽 끝이 1480인데 이 카드만 1380에서 끝나, 중간 줄만 안으로 들어가 보였습니다. 재서 확인했습니다(세 블록 모두 1480, 카드 폭 360).text-muted-foreground/70이라 흰 카드 위에서 WCAG 기준(본문 4.5:1) 미달이었습니다. 알파를 걷어내고 12px로 키웠습니다.--secondary(#fafaf9)가 카드 흰색과 거의 같아/50까지 씌우면 대비가 1.02:1입니다. 띠는 두되(다크에선 보입니다) 아래 보더를 더했습니다.🎨 운영자 화면을 다크 기본으로 (2026-08-09 추가)
하루 종일 큐·실패 목록을 보는 화면이라 밝은 바탕이 오래 보기 힘들고, 사용자 화면과 섞이지 않는다는 신호도 됩니다. 바꿀 수는 있습니다 — 상단바 토글은 그대로 살아 있고, 그 선택은
/system셸 안에서만 삽니다..dark를<html>이 아니라 셸 상자에 붙입니다. globals.css의 다크 토큰이 클래스 선택자(.dark {})라 상자에 붙이면 그 안쪽만 뒤집히고,dark:유틸리티도&:is(.dark *)라 함께 따라옵니다.<html>에 쓰므로 둘이 같은 자리를 두고 싸웁니다.#1a1715) / 토글 → light 쿠키 기록 + 즉시 반영 / 새로고침 → 유지 /<html>의 다크 클래스는 끝까지 안 건드림 / 로그인 화면 등 다른 라우트 영향 없음.셸 밖에 사는 것도 맞췄습니다(리뷰 반영). 토스트(
<Toaster />)는 루트 레이아웃에 하나만 두는 규칙이라 이 상자 밖에 있어서, 전역 밝기로 색을 계산했습니다 — 다크로 보는 운영자 화면 위에 라이트 값으로 만든 알약이 떠서 바탕과 1.4:1이었습니다. 상자가 떠 있는 동안 같은 밝기를<body>에도 걸고, 벗어나면 지웁니다.곁들여 두 가지를 더 막았습니다.
.light). 다크만 붙이면 전역이 다크인 사람은 토글을 눌러도 상자에 아무 선언이 없어html.dark값을 그대로 물려받습니다 — globals.css의 라이트 토큰 선택자를:root, .light로 넓혔습니다.&:is(.dark *):not(.light *)). 변수만 되돌리면dark:선언은 계속 이겨서, 밝은 화면에 다크용 표면(dark:bg-input/30등)이 섞였습니다.Closes #238