Skip to content

[FEAT] 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 - #71

Merged
hyun030514 merged 3 commits into
developfrom
feat/system-monitor
Aug 4, 2026
Merged

hyun030514 merged 3 commits into
developfrom
feat/system-monitor

Conversation

@CallmeEterHist

@CallmeEterHist CallmeEterHist commented Aug 3, 2026 •

Copy link
Copy Markdown
Member

📋 PR 타입

  • feature — 새로운 기능 추가
  • fix
  • style
  • refactor
  • docs
  • test
  • design — UI/UX 추가
  • chore
  • merge

🗂 작업 영역

  • 조직 — 역할별 대시보드 · 사원/회의실 관리 · 구독 ← SYSTEM 운영 도구

🙋 관련 역할

  • SYSTEM (서비스 운영자 — 확장, 데모 제외)

📝 작업 내용

무엇을 · 왜

  • /system/monitor 신규 — 처리 큐 요약 · 단계별 평균 소요 · 실패 재처리
  • 격리막(types → mock → server → actions)으로 구성, BE 연동 시 매퍼만 수정
  • 도메인 규격(카드/표 크기·토큰) 준수, 초안의 생 hex → 디자인 토큰 치환(다크 대응)
  • 실패 목록 표는 next/dynamic으로 청크 분리 (상호작용 잎사귀만 client)
  • StatCard에 danger tone 추가(실패 강조), 사이드바 nav /system/monitor 활성화

✅ 작업 체크리스트

기본

  • 브랜치 명명 규칙 (feature/system-monitor#{이슈번호}, base develop)
  • 커밋 컨벤션 (feat: 시스템 모니터링 화면 #{이슈번호})
  • 아래 Closes #
  • 불필요한 console · 주석 코드 없음
  • 민감 정보 없음

Z 필수

  • 🔐 권한 — SYSTEM 전용 라우트 그룹 (소유권 축 해당 없음)
  • 🧬 zod — 목 단계, ERD 확정 후 매퍼에 safeParse 예정(격리막 유지)
  • 🕵️ 정직성 — 목/미구현/미발송 전부 주석·토스트에 명시
  • 🎨 디자인 토큰 사용 (생 hex 없음) · 다크 값 정의됨

품질

  • ⚡ 최적화 — 실패 표 next/dynamic 청크 분리 · 서버 컴포넌트 우선
  • ♿ a11y — h1 1개(레이아웃) · 버튼 이름 · 표 구조 · 키보드 동작
  • ♻️ 재사용 — StatCard·shadcn Table·Skeleton·ScreenError 재사용
  • 🧪 loading / error / empty 3상태
  • 브라우저 전용 API — 해당 없음(파이프라인 "단계"는 서버 잡)

🔗 연관 이슈

Closes #70


📸 스크린샷 (선택)

Before After
(없음) (시스템 모니터링 화면)

💬 리뷰 포인트

  • 실패 표 dynamic import에서 ssr:false를 일부러 뺀 이유: 이 표는 브라우저 전용
    API가 아니라 SSR돼야 첫 화면이 비지 않음. ssr:false로 두면 하드 로드 시 스켈레톤이
    안 풀리는 문제 확인 → 코드 스플릿만 유지(주석에 근거 명시).
  • StatCard에 danger tone 추가가 다른 화면에 영향 없는지(기본값 neutral 유지).

📝 추가 메모

Summary by CodeRabbit

  • 신규 기능

    • 시스템 모니터링 화면을 추가해 큐 현황, 단계별 처리 시간, 실패 파이프라인을 확인할 수 있습니다.
    • 실패한 파이프라인을 재처리하고 결과를 확인할 수 있습니다.
    • 미납 안내 메일 발송 진행 상태와 성공·실패 알림을 개선했습니다.
  • 개선 사항

    • 시스템 대시보드와 관리 화면의 여백과 정보 밀도를 조정했습니다.
    • 상태 배지와 주요 통계 카드의 색상 표현을 개선했습니다.
    • 테이블과 차트 크기, 글꼴, 정렬을 조정해 화면을 더욱 간결하게 구성했습니다.
    • 시스템 모니터링 화면에 로딩 및 오류 상태를 추가했습니다.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

시스템 모니터링 라우트에 큐 통계, 단계별 처리 시간, 실패 파이프라인 목록과 재처리 기능을 추가했습니다. 모니터링 상태 화면과 목 데이터 조회를 구성했습니다. 기존 시스템 화면의 간격, 크기, 상태 표현과 미납 안내 흐름도 조정했습니다.

Changes

시스템 모니터링 기능

Layer / File(s) Summary
모니터링 데이터 계약과 조회
src/constants/domain.ts, src/features/system/types.ts, src/features/system/mock/monitoring.ts, src/features/system/server.ts, src/features/system/nav.ts
파이프라인 단계와 모니터링 데이터 타입을 추가했습니다. 목 데이터 조회와 실패 항목 검색을 구성했습니다. 메뉴 경로를 /system/monitor로 변경했습니다.
모니터링 화면과 상태 UI
src/app/(system)/system/monitor/*, src/features/system/components/stage-timing-card.tsx, src/features/system/components/failed-pipeline-table*.tsx
페이지 헤더, 메타데이터, 로딩·오류 상태를 추가했습니다. 큐 통계, 단계별 평균 시간, 실패 목록과 빈 상태를 렌더링합니다.
실패 파이프라인 재처리
src/features/system/actions.ts, src/features/system/components/pipeline-retry-button.tsx
실패 항목의 목 재처리 액션을 추가했습니다. 처리 중 버튼 비활성화, 완료 상태, 성공·실패 토스트를 구현했습니다.

기존 시스템 화면 정비

Layer / File(s) Summary
상태 표현과 미납 안내 흐름
src/features/system/components/status-badge.tsx, src/features/system/components/stat-card.tsx, src/features/system/components/notice-mail-dialog.tsx, src/features/system/components/subscription-table.tsx, src/features/system/actions.ts
상태별 StatusBadge와 StatCard 톤을 추가했습니다. 미납 안내 다이얼로그가 외부 콜백과 pending 상태를 사용하도록 변경했습니다.
시스템 화면 밀도와 시각 스타일
src/app/(system)/*, src/app/globals.css, src/features/system/components/*, src/features/system/mock/billing.ts, src/features/system/mock/companies.ts
시스템 페이지의 패딩, 간격, 행 높이, 글꼴, 차트 크기와 사이드바 스타일을 축소했습니다. 운영자용 점 그리드 배경과 동적 미납 건수를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SystemMonitorPage
  participant getMonitoringOverview
  participant MOCK_MONITORING_OVERVIEW
  participant FailedPipelineTable
  participant PipelineRetryButton
  participant retryPipelineAction
  SystemMonitorPage->>getMonitoringOverview: 모니터링 개요 조회
  getMonitoringOverview->>MOCK_MONITORING_OVERVIEW: 목 데이터 반환
  getMonitoringOverview-->>SystemMonitorPage: 큐·단계 시간·실패 항목
  SystemMonitorPage->>FailedPipelineTable: 실패 항목 렌더링
  FailedPipelineTable->>PipelineRetryButton: meetingId 전달
  PipelineRetryButton->>retryPipelineAction: 재처리 요청
Loading

Possibly related PRs

Suggested labels: ui

Suggested reviewers: zana0422-creator, hyun030514

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 대시보드·승인·청구·회사 화면의 스타일 변경과 미납 메일 흐름 변경이 모니터링 기능과 함께 포함되어 범위를 벗어납니다. 관련 없는 화면 및 미납 메일 변경을 별도 PR로 분리하고, 모니터링 구현과 필요한 공통 컴포넌트 변경만 유지하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 69.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 시스템 모니터링 화면, 처리 큐, 단계 소요 시간, 실패 재처리라는 주요 변경 사항을 명확히 설명합니다.
Linked Issues check ✅ Passed 처리 큐 요약, 단계별 소요 시간, 실패 재처리, 상태 화면, 동적 분리, 목 연동을 구현해 #70의 목표를 충족합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/system-monitor

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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/monitor/layout.tsx:
- Line 6: Replace the inline props object types with named interfaces and apply
them to the component parameters: declare and use SystemMonitorLayoutProps in
src/app/(system)/system/monitor/layout.tsx:6-6, ErrorProps in
src/app/(system)/system/monitor/error.tsx:5-5, and
FailedPipelineTableLoaderProps in
src/features/system/components/failed-pipeline-table-loader.tsx:24-24,
preserving each component’s existing props.

In `@src/features/system/components/approval-table.tsx`:
- Around line 83-87: 공용 TableHead의 기본 높이 대신 각 표의 34px 헤더 높이 계약을 적용하세요.
src/features/system/components/approval-table.tsx 83-87의 5개 TableHead와
src/features/system/components/company-table.tsx 89-95의 7개 TableHead 모두에
h-[34px] 클래스를 추가하고 기존 스타일은 유지하세요.

In `@src/features/system/components/company-table.tsx`:
- Around line 129-132: Update StatusBadge and its status color tokens so light
mode uses darker foreground colors that meet 4.5:1 contrast against the
translucent backgrounds, while dark mode retains separate appropriate foreground
tokens. Ensure the STATUS_TONE mappings used by the company table resolve
through these light/dark-aware tokens.

In `@src/features/system/components/pipeline-retry-button.tsx`:
- Around line 36-46: Update handleRetry to wrap the retryPipelineAction call and
response handling in a local try/catch. When the action throws, show the
retry-failed error toast instead of allowing the error to propagate to the Error
Boundary; preserve the existing success and unsuccessful-response behavior.

In `@src/features/system/components/recent-companies-table.tsx`:
- Around line 31-35: Update the header cells in the recent companies table by
adding h-[34px] to each TableHead, ensuring all four headers match the
TableRow’s intended 34px height.

In `@src/features/system/components/stage-timing-card.tsx`:
- Around line 30-47: Update the stage bar width calculation in the timings map
to handle maxSeconds being zero, ensuring the initial all-zero state produces a
valid bounded width instead of NaN%. Preserve the existing relative-width
calculation when maxSeconds is greater than zero, using the existing maxSeconds
and timing.avgSeconds values.

In `@src/features/system/components/subscription-table.tsx`:
- Around line 68-81: Update handleConfirm’s startTransition flow to wrap
sendUnpaidNoticeAction in try/catch/finally, showing the failure toast when the
action throws and always resetting noticeTarget in finally. Preserve the
existing success and unsuccessful-response toast behavior for completed calls.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a3759175-c58c-4c55-8a68-6f6ae8dbd46d

📥 Commits

Reviewing files that changed from the base of the PR and between 926ad35 and ac64d0b.

📒 Files selected for processing (40)
  • src/app/(system)/layout.tsx
  • src/app/(system)/system/(dashboard)/page.tsx
  • src/app/(system)/system/approval/page.tsx
  • src/app/(system)/system/billing/page.tsx
  • src/app/(system)/system/companies/page.tsx
  • src/app/(system)/system/monitor/error.tsx
  • src/app/(system)/system/monitor/layout.tsx
  • src/app/(system)/system/monitor/loading.tsx
  • src/app/(system)/system/monitor/page.tsx
  • src/app/globals.css
  • src/constants/domain.ts
  • src/features/system/actions.ts
  • src/features/system/components/approval-table.tsx
  • src/features/system/components/company-filter-bar.tsx
  • src/features/system/components/company-table.tsx
  • src/features/system/components/failed-pipeline-table-loader.tsx
  • src/features/system/components/failed-pipeline-table.tsx
  • src/features/system/components/mrr-chart-card.tsx
  • src/features/system/components/mrr-chart-loader.tsx
  • src/features/system/components/mrr-chart.tsx
  • src/features/system/components/notice-mail-dialog.tsx
  • src/features/system/components/pipeline-retry-button.tsx
  • 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
  • src/features/system/components/recent-companies-table.tsx
  • src/features/system/components/signup-chart-card.tsx
  • src/features/system/components/signup-chart-loader.tsx
  • src/features/system/components/signup-chart.tsx
  • src/features/system/components/stage-timing-card.tsx
  • src/features/system/components/stat-card.tsx
  • src/features/system/components/status-badge.tsx
  • src/features/system/components/subscription-table.tsx
  • src/features/system/components/system-sidebar.tsx
  • src/features/system/mock/billing.ts
  • src/features/system/mock/companies.ts
  • src/features/system/mock/monitoring.ts
  • src/features/system/nav.ts
  • src/features/system/server.ts
  • src/features/system/types.ts


import { PageHeader } from "@/features/shell/components/page-header";

export default function SystemMonitorLayout({ children }: { children: ReactNode }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

props 타입을 interface로 선언하세요.

세 컴포넌트가 props를 인라인 객체 타입으로 선언합니다. 각 파일에 명명된 props interface를 선언하고 컴포넌트 매개변수에 적용하세요.

  • src/app/(system)/system/monitor/layout.tsx#L6-L6: SystemMonitorLayoutProps interface를 선언하세요.
  • src/app/(system)/system/monitor/error.tsx#L5-L5: ErrorProps interface를 선언하세요.
  • src/features/system/components/failed-pipeline-table-loader.tsx#L24-L24: FailedPipelineTableLoaderProps interface를 선언하세요.

As per coding guidelines, "props 타입을 인터페이스로 명시하고 any를 사용하지 않는다."

📍 Affects 3 files
  • src/app/(system)/system/monitor/layout.tsx#L6-L6 (this comment)
  • src/app/(system)/system/monitor/error.tsx#L5-L5
  • src/features/system/components/failed-pipeline-table-loader.tsx#L24-L24
🤖 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/monitor/layout.tsx at line 6, Replace the inline
props object types with named interfaces and apply them to the component
parameters: declare and use SystemMonitorLayoutProps in
src/app/(system)/system/monitor/layout.tsx:6-6, ErrorProps in
src/app/(system)/system/monitor/error.tsx:5-5, and
FailedPipelineTableLoaderProps in
src/features/system/components/failed-pipeline-table-loader.tsx:24-24,
preserving each component’s existing props.

Source: Coding guidelines

Comment on lines +83 to +87
<TableHead className="pl-4 text-xs">회사명</TableHead>
<TableHead className="text-center text-xs">대표자</TableHead>
<TableHead className="text-center text-xs">담당자 이메일</TableHead>
<TableHead className="text-center text-xs">규모</TableHead>
<TableHead className="pr-4 text-center text-xs">신청일</TableHead>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

공용 TableHead의 기본 높이와 각 표의 34px 계약을 일치시키세요.

두 표가 헤더 높이를 34px로 계산하지만, 헤더 셀은 공용 h-10을 그대로 사용합니다. 모든 헤더 셀에 h-[34px]를 적용하세요.

  • src/features/system/components/approval-table.tsx#L83-L87: 5개 TableHead에 h-[34px]를 추가하세요.
  • src/features/system/components/company-table.tsx#L89-L95: 7개 TableHead에 h-[34px]를 추가하세요.
📍 Affects 2 files
  • src/features/system/components/approval-table.tsx#L83-L87 (this comment)
  • src/features/system/components/company-table.tsx#L89-L95
🤖 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/approval-table.tsx` around lines 83 - 87, 공용
TableHead의 기본 높이 대신 각 표의 34px 헤더 높이 계약을 적용하세요.
src/features/system/components/approval-table.tsx 83-87의 5개 TableHead와
src/features/system/components/company-table.tsx 89-95의 7개 TableHead 모두에
h-[34px] 클래스를 추가하고 기존 스타일은 유지하세요.

Comment on lines +129 to +132
<TableCell className="pr-4 text-center">
<StatusBadge tone={STATUS_TONE[company.status]}>
{COMPANY_STATUS_LABEL[company.status]}
</Badge>
</StatusBadge>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

라이트 모드 상태 배지의 텍스트 대비를 높이세요.

이 변경은 Badge 대신 StatusBadge를 사용합니다. StatusBadge는 text-success, text-warning, text-destructive를 10% 또는 12% 반투명 배경에 사용합니다. 현재 라이트 토큰 #22c55e, #f59e0b, #ef4444는 text-xs 상태 라벨에서 4.5:1 대비를 충족하지 않습니다. 라이트 모드용 어두운 전경 토큰과 다크 모드용 전경 토큰을 분리하고 StatusBadge가 해당 토큰을 사용하도록 수정하세요.

🤖 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/company-table.tsx` around lines 129 - 132,
Update StatusBadge and its status color tokens so light mode uses darker
foreground colors that meet 4.5:1 contrast against the translucent backgrounds,
while dark mode retains separate appropriate foreground tokens. Ensure the
STATUS_TONE mappings used by the company table resolve through these
light/dark-aware tokens.

Comment on lines +36 to +46
const handleRetry = () => {
startTransition(async () => {
const response = await retryPipelineAction(meetingId);
if (response.success) {
setIsDone(true);
toast(`${companyName} 회의 재처리를 요청했어요`);
} else {
toast(`${companyName} 회의를 찾을 수 없어 재처리하지 못했어요`);
}
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
fd -i 'pipeline-retry-button|actions\.ts|action' src | head -80

printf '%s\n' '--- component ---'
component="$(fd -i 'pipeline-retry-button\.tsx' src | head -1)"
if [ -n "$component" ]; then
  cat -n "$component"
fi

printf '%s\n' '--- retryPipelineAction definitions/usages ---'
rg -n -C 8 'retryPipelineAction|retryPipeline' src

Repository: Z-Groupware/FRONTEND

Length of output: 6254


🌐 Web query:

React 19 official docs startTransition async function errors Error Boundary catch errors inside Actions

💡 Result:

In React 19, error handling for async functions within Actions (specifically those triggered via the startTransition returned by useTransition) has been improved to allow integration with Error Boundaries [1][2][3]. Key points regarding this behavior: 1. Automatic Propagation: When an error is thrown inside an async function passed to the startTransition returned by useTransition, React automatically catches the error and propagates it to the nearest Error Boundary [2][4][3]. This provides a declarative way to handle async errors without manually managing try/catch blocks or state variables in every component [2][5]. 2. Error Boundaries: Standard Error Boundaries continue to function as expected, catching rendering errors [6][2]. In React 19, they also catch these async errors originating from Actions, allowing you to display a fallback UI and offer users a way to retry the operation [5][3]. 3. startTransition (Standalone): When using the standalone React.startTransition (outside of a component/hook context), behavior differs. Errors thrown within this function are generally swallowed by React and reported to the global reportError function to prevent unhandled promise rejections [7][8]. If you need to catch errors locally when using the standalone startTransition, you should use a try/catch block inside the callback function itself [7][8]. 4. Recommended Patterns: While Actions support throwing errors for Error Boundary catch, it is often recommended to treat expected failures (like validation errors or API errors) as part of your state [9]. By returning an error object from your action (often used with useActionState), you can maintain UI responsiveness and show inline error messages without "breaking" the component tree or triggering the Error Boundary unnecessarily [9][5]. Throwing is best reserved for truly unexpected or critical errors [9]. These changes reduce the need for manual plumbing of loading and error states, making applications more resilient and easier to maintain [5][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- React versions ---'
rg -n '"react"|"react-dom"|"next"' package.json '**/package.json' 2>/dev/null || true

printf '%s\n' '--- error boundaries ---'
fd -HI 'error\.(tsx|ts|jsx|js)$|global-error\.(tsx|ts|jsx|js)$' . | head -80

printf '%s\n' '--- action context ---'
cat -n src/features/system/actions.ts | sed -n '1,135p'

printf '%s\n' '--- system route files ---'
fd -HI . src app pages 2>/dev/null | rg 'system|error\.(tsx|ts)$' | head -120

Repository: Z-Groupware/FRONTEND

Length of output: 13699


🏁 Script executed:

#!/bin/bash
set -eu

cat -n 'src/app/(system)/system/monitor/error.tsx'
printf '%s\n' '--- shared error UI references ---'
rg -n -C 5 'ScreenError|error\.message|reset\(' 'src/app/(system)/system/monitor' src/components/common

Repository: Z-Groupware/FRONTEND

Length of output: 2397


retryPipelineAction의 예외를 로컬에서 처리하세요.

isMock === false이면 액션이 Error를 throw합니다. 현재 try/catch가 없으므로 React 19의 Action 오류가 모니터링 라우트의 Error Boundary로 전달될 수 있습니다. handleRetry에서 호출을 try/catch로 감싸고, 실패 시 재처리 오류 토스트를 표시하세요.

🤖 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/pipeline-retry-button.tsx` around lines 36 -
46, Update handleRetry to wrap the retryPipelineAction call and response
handling in a local try/catch. When the action throws, show the retry-failed
error toast instead of allowing the error to propagate to the Error Boundary;
preserve the existing success and unsuccessful-response behavior.

Comment on lines +31 to +35
<TableRow className="h-[34px] hover:bg-transparent">
<TableHead className="pl-4 text-xs">기업명</TableHead>
<TableHead className="text-center text-xs">플랜</TableHead>
<TableHead className="text-center text-xs">구성원</TableHead>
<TableHead className="pr-4 text-center text-xs">가입일</TableHead>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

헤더 높이를 TableHead에도 지정하세요.

src/components/ui/table.tsx의 TableHead는 기본 h-10(40px)을 사용합니다. 현재 코드는 TableRow에만 h-[34px]을 지정합니다. 헤더 셀이 40px 높이를 유지하여 의도한 34px 행 높이가 적용되지 않을 수 있습니다.

각 TableHead에 h-[34px]을 추가하세요.

수정 예시
-            <TableHead className="pl-4 text-xs">기업명</TableHead>
-            <TableHead className="text-center text-xs">플랜</TableHead>
-            <TableHead className="text-center text-xs">구성원</TableHead>
-            <TableHead className="pr-4 text-center text-xs">가입일</TableHead>
+            <TableHead className="h-[34px] pl-4 text-xs">기업명</TableHead>
+            <TableHead className="h-[34px] text-center text-xs">플랜</TableHead>
+            <TableHead className="h-[34px] text-center text-xs">구성원</TableHead>
+            <TableHead className="h-[34px] pr-4 text-center text-xs">가입일</TableHead>
📝 Committable suggestion

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

Suggested change
<TableRow className="h-[34px] hover:bg-transparent">
<TableHead className="pl-4 text-xs">기업명</TableHead>
<TableHead className="text-center text-xs">플랜</TableHead>
<TableHead className="text-center text-xs">구성원</TableHead>
<TableHead className="pr-4 text-center text-xs">가입일</TableHead>
<TableRow className="h-[34px] hover:bg-transparent">
<TableHead className="h-[34px] pl-4 text-xs">기업명</TableHead>
<TableHead className="h-[34px] text-center text-xs">플랜</TableHead>
<TableHead className="h-[34px] text-center text-xs">구성원</TableHead>
<TableHead className="h-[34px] pr-4 text-center text-xs">가입일</TableHead>
🤖 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/recent-companies-table.tsx` around lines 31 -
35, Update the header cells in the recent companies table by adding h-[34px] to
each TableHead, ensuring all four headers match the TableRow’s intended 34px
height.

Comment on lines +30 to +47
// 가장 오래 걸린 단계를 100%로 잡아 나머지를 상대 길이로 그린다.
const maxSeconds = Math.max(...timings.map((timing) => timing.avgSeconds));

return (
<section className="border-border bg-card rounded-xl border p-5">
<h2 className="text-foreground text-sm font-semibold">단계별 평균 소요 시간</h2>

<div className="mt-4 flex flex-col gap-3">
{timings.map((timing) => (
<div key={timing.stage} className="flex items-center gap-3">
<span className="text-muted-foreground w-20 shrink-0 text-xs">
{PIPELINE_STAGE_LABEL[timing.stage]}
</span>
<div className="bg-muted h-4 flex-1 overflow-hidden rounded-full">
<div
className={`h-full rounded-full transition-[width] duration-[250ms] ${STAGE_BAR_CLASS[timing.stage]}`}
style={{ width: `${(timing.avgSeconds / maxSeconds) * 100}%` }}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

maxSeconds가 0이면 막대 너비가 NaN%가 됩니다.

모든 단계의 avgSeconds가 0인 경우(아직 완료된 처리가 없는 초기 상태), maxSeconds도 0이 됩니다. Line 46의 (timing.avgSeconds / maxSeconds) * 100은 0 / 0이 되어 NaN을 반환합니다. 이 값은 style={{ width: "NaN%" }}로 렌더링되어 막대가 깨집니다.

maxSeconds가 0일 때를 방어하는 코드를 추가하세요.

🐛 제안하는 수정
   // 가장 오래 걸린 단계를 100%로 잡아 나머지를 상대 길이로 그린다.
-  const maxSeconds = Math.max(...timings.map((timing) => timing.avgSeconds));
+  const maxSeconds = Math.max(...timings.map((timing) => timing.avgSeconds), 1);
📝 Committable suggestion

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

Suggested change
// 가장 오래 걸린 단계를 100%로 잡아 나머지를 상대 길이로 그린다.
const maxSeconds = Math.max(...timings.map((timing) => timing.avgSeconds));
return (
<section className="border-border bg-card rounded-xl border p-5">
<h2 className="text-foreground text-sm font-semibold">단계별 평균 소요 시간</h2>
<div className="mt-4 flex flex-col gap-3">
{timings.map((timing) => (
<div key={timing.stage} className="flex items-center gap-3">
<span className="text-muted-foreground w-20 shrink-0 text-xs">
{PIPELINE_STAGE_LABEL[timing.stage]}
</span>
<div className="bg-muted h-4 flex-1 overflow-hidden rounded-full">
<div
className={`h-full rounded-full transition-[width] duration-[250ms] ${STAGE_BAR_CLASS[timing.stage]}`}
style={{ width: `${(timing.avgSeconds / maxSeconds) * 100}%` }}
/>
// 가장 오래 걸린 단계를 100%로 잡아 나머지를 상대 길이로 그린다.
const maxSeconds = Math.max(...timings.map((timing) => timing.avgSeconds), 1);
return (
<section className="border-border bg-card rounded-xl border p-5">
<h2 className="text-foreground text-sm font-semibold">단계별 평균 소요 시간</h2>
<div className="mt-4 flex flex-col gap-3">
{timings.map((timing) => (
<div key={timing.stage} className="flex items-center gap-3">
<span className="text-muted-foreground w-20 shrink-0 text-xs">
{PIPELINE_STAGE_LABEL[timing.stage]}
</span>
<div className="bg-muted h-4 flex-1 overflow-hidden rounded-full">
<div
className={`h-full rounded-full transition-[width] duration-[250ms] ${STAGE_BAR_CLASS[timing.stage]}`}
style={{ width: `${(timing.avgSeconds / maxSeconds) * 100}%` }}
/>
🤖 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/stage-timing-card.tsx` around lines 30 - 47,
Update the stage bar width calculation in the timings map to handle maxSeconds
being zero, ensuring the initial all-zero state produces a valid bounded width
instead of NaN%. Preserve the existing relative-width calculation when
maxSeconds is greater than zero, using the existing maxSeconds and
timing.avgSeconds values.

Comment on lines +68 to +81
const handleConfirm = (companyId: string) => {
const target = noticeTarget;
if (!target) return;

startTransition(async () => {
const response = await sendUnpaidNoticeAction(companyId);
if (response.success) {
toast(`${target.companyName} 담당자에게 안내 메일을 발송했어요`);
} else {
toast(`${target.companyName} 정보를 찾을 수 없어 발송하지 못했어요`);
}
setNoticeTarget(null);
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does an unhandled error thrown inside React 19 startTransition propagate to the nearest error boundary?

💡 Result:

Yes, in React 19, an error thrown inside a function passed to startTransition (whether sync or async) will propagate to the nearest error boundary [1][2][3][4]. React 19 introduced this integration to improve error handling for Actions [5][4]. When you provide a function to startTransition—which is returned by useTransition or the standalone React.startTransition—React treats errors thrown within that function as catchable by the nearest error boundary [1][2][3]. This allows you to manage async errors more cleanly without needing to manually use try/catch blocks to set error state [5][2]. It is important to note that this behavior applies specifically to functions passed into startTransition as part of the React 19 Actions pattern [1][3][4]. If an error occurs outside of this controlled transition scope (e.g., in a standard event handler), it will not be caught by an error boundary [1][6]. Additionally, in React 19, errors thrown inside startTransition that are not caught by an error boundary are passed to the global reportError function [7][8]. React has moved away from rethrowing these errors to the root to avoid inconsistencies in error logging [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- target component ---'
sed -n '1,120p' src/features/system/components/subscription-table.tsx
printf '%s\n' '--- action implementation ---'
sed -n '1,140p' src/features/system/actions.ts
printf '%s\n' '--- React/package versions and usages ---'
rg -n '"react"|"next"|sendUnpaidNoticeAction|isMock|ErrorBoundary|error\.tsx|global-error' package.json package-lock.json pnpm-lock.yaml yarn.lock src 2>/dev/null | head -240

Repository: Z-Groupware/FRONTEND

Length of output: 20374


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- relevant component remainder ---'
sed -n '120,240p' src/features/system/components/subscription-table.tsx
printf '%s\n' '--- dialog implementation ---'
sed -n '1,220p' src/features/system/components/notice-mail-dialog.tsx
printf '%s\n' '--- error boundaries and related transition handlers ---'
find src -type f \( -name 'error.tsx' -o -name 'global-error.tsx' \) -print
sed -n '1,180p' src/features/system/components/pipeline-retry-button.tsx
printf '%s\n' '--- action call sites ---'
rg -n -C 8 'handleConfirm|sendUnpaidNoticeAction|onConfirm' src/features/system

Repository: Z-Groupware/FRONTEND

Length of output: 17769


handleConfirm에 예외 처리를 추가하세요.

sendUnpaidNoticeAction은 isMock === false일 때 예외를 던집니다. 예외가 처리되지 않으면 시스템 청구 화면의 에러 바운더리로 전파되고, 실패 토스트가 표시되지 않습니다. setNoticeTarget(null)도 실행되지 않아 다이얼로그가 열린 상태로 남습니다. try/catch/finally로 감싸서 실패를 토스트로 알리고 대상 상태를 항상 초기화하세요.

🤖 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/subscription-table.tsx` around lines 68 - 81,
Update handleConfirm’s startTransition flow to wrap sendUnpaidNoticeAction in
try/catch/finally, showing the failure toast when the action throws and always
resetting noticeTarget in finally. Preserve the existing success and
unsuccessful-response toast behavior for completed calls.

@hyun030514 hyun030514 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

확인했습니다.

@hyun030514
hyun030514 merged commit 1994e13 into develop Aug 4, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Z Aug 4, 2026
@hyun030514 hyun030514 changed the title [FEAT] 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 feat: 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 Aug 13, 2026
@hyun030514 hyun030514 changed the title feat: 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 [FEAT] 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리)

2 participants