Repository navigation
[FEAT] 시스템 모니터링 화면 (처리 큐·단계 소요·실패 재처리) #70 - #71
Conversation
📝 WalkthroughWalkthrough시스템 모니터링 라우트에 큐 통계, 단계별 처리 시간, 실패 파이프라인 목록과 재처리 기능을 추가했습니다. 모니터링 상태 화면과 목 데이터 조회를 구성했습니다. 기존 시스템 화면의 간격, 크기, 상태 표현과 미납 안내 흐름도 조정했습니다. Changes시스템 모니터링 기능
기존 시스템 화면 정비
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: 재처리 요청
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (40)
src/app/(system)/layout.tsxsrc/app/(system)/system/(dashboard)/page.tsxsrc/app/(system)/system/approval/page.tsxsrc/app/(system)/system/billing/page.tsxsrc/app/(system)/system/companies/page.tsxsrc/app/(system)/system/monitor/error.tsxsrc/app/(system)/system/monitor/layout.tsxsrc/app/(system)/system/monitor/loading.tsxsrc/app/(system)/system/monitor/page.tsxsrc/app/globals.csssrc/constants/domain.tssrc/features/system/actions.tssrc/features/system/components/approval-table.tsxsrc/features/system/components/company-filter-bar.tsxsrc/features/system/components/company-table.tsxsrc/features/system/components/failed-pipeline-table-loader.tsxsrc/features/system/components/failed-pipeline-table.tsxsrc/features/system/components/mrr-chart-card.tsxsrc/features/system/components/mrr-chart-loader.tsxsrc/features/system/components/mrr-chart.tsxsrc/features/system/components/notice-mail-dialog.tsxsrc/features/system/components/pipeline-retry-button.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-loader.tsxsrc/features/system/components/signup-chart.tsxsrc/features/system/components/stage-timing-card.tsxsrc/features/system/components/stat-card.tsxsrc/features/system/components/status-badge.tsxsrc/features/system/components/subscription-table.tsxsrc/features/system/components/system-sidebar.tsxsrc/features/system/mock/billing.tssrc/features/system/mock/companies.tssrc/features/system/mock/monitoring.tssrc/features/system/nav.tssrc/features/system/server.tssrc/features/system/types.ts
|
|
||
| import { PageHeader } from "@/features/shell/components/page-header"; | ||
|
|
||
| export default function SystemMonitorLayout({ children }: { children: ReactNode }) { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
props 타입을 interface로 선언하세요.
세 컴포넌트가 props를 인라인 객체 타입으로 선언합니다. 각 파일에 명명된 props interface를 선언하고 컴포넌트 매개변수에 적용하세요.
src/app/(system)/system/monitor/layout.tsx#L6-L6:SystemMonitorLayoutPropsinterface를 선언하세요.src/app/(system)/system/monitor/error.tsx#L5-L5:ErrorPropsinterface를 선언하세요.src/features/system/components/failed-pipeline-table-loader.tsx#L24-L24:FailedPipelineTableLoaderPropsinterface를 선언하세요.
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-L5src/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
| <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> |
There was a problem hiding this comment.
🎯 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] 클래스를 추가하고 기존 스타일은 유지하세요.
| <TableCell className="pr-4 text-center"> | ||
| <StatusBadge tone={STATUS_TONE[company.status]}> | ||
| {COMPANY_STATUS_LABEL[company.status]} | ||
| </Badge> | ||
| </StatusBadge> |
There was a problem hiding this comment.
🎯 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.
| const handleRetry = () => { | ||
| startTransition(async () => { | ||
| const response = await retryPipelineAction(meetingId); | ||
| if (response.success) { | ||
| setIsDone(true); | ||
| toast(`${companyName} 회의 재처리를 요청했어요`); | ||
| } else { | ||
| toast(`${companyName} 회의를 찾을 수 없어 재처리하지 못했어요`); | ||
| } | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🩺 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' srcRepository: 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:
- 1: https://react.dev/blog/2024/12/05/react-19
- 2: https://github.com/bvaughn/react-error-boundary/blob/main/README.md
- 3: https://certificates.dev/blog/error-handling-in-react-with-react-error-boundary
- 4: https://blog.openreplay.com/react-19-async-transitions/
- 5: https://medium.com/@connect.hashblock/react-19-resilience-retry-suspense-error-boundaries-40ea504b09ed
- 6: https://github.com/bvaughn/react-error-boundary
- 7: Capture React.startTransition errors and pass to reportError react/react#28111
- 8: [Fiber] Don't Rethrow Errors at the Root react/react#28627
- 9: https://reactz2h.com/chapter_08_react_19_and_concurrent_features/series_03_react_actions_and_form_state/error_handling_actions
🏁 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 -120Repository: 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/commonRepository: 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.
| <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> |
There was a problem hiding this comment.
🎯 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.
| <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.
| // 가장 오래 걸린 단계를 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}%` }} | ||
| /> |
There was a problem hiding this comment.
🎯 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.
| // 가장 오래 걸린 단계를 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.
| 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); | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🩺 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:
- 1: https://github.com/bvaughn/react-error-boundary/blob/main/README.md
- 2: https://blog.openreplay.com/react-19-async-transitions/
- 3: https://uk.react.dev/reference/react/useTransition
- 4: Refresh error boundary docs and examples bvaughn/react-error-boundary#240
- 5: https://react.dev/blog/2024/12/05/react-19
- 6: https://github.com/bvaughn/react-error-boundary
- 7: Capture React.startTransition errors and pass to reportError react/react#28111
- 8: [Fiber] Don't Rethrow Errors at the Root react/react#28627
🏁 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 -240Repository: 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/systemRepository: 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.
📋 PR 타입
🗂 작업 영역
🙋 관련 역할
📝 작업 내용
/system/monitor신규 — 처리 큐 요약 · 단계별 평균 소요 · 실패 재처리next/dynamic으로 청크 분리 (상호작용 잎사귀만 client)StatCard에dangertone 추가(실패 강조), 사이드바 nav/system/monitor활성화✅ 작업 체크리스트
기본
feature/system-monitor#{이슈번호}, basedevelop)feat: 시스템 모니터링 화면 #{이슈번호})Closes #Z 필수
safeParse예정(격리막 유지)품질
next/dynamic청크 분리 · 서버 컴포넌트 우선h11개(레이아웃) · 버튼 이름 · 표 구조 · 키보드 동작StatCard·shadcnTable·Skeleton·ScreenError재사용🔗 연관 이슈
Closes #70
📸 스크린샷 (선택)
💬 리뷰 포인트
dynamic import에서ssr:false를 일부러 뺀 이유: 이 표는 브라우저 전용API가 아니라 SSR돼야 첫 화면이 비지 않음.
ssr:false로 두면 하드 로드 시 스켈레톤이안 풀리는 문제 확인 → 코드 스플릿만 유지(주석에 근거 명시).
StatCard에dangertone 추가가 다른 화면에 영향 없는지(기본값neutral유지).📝 추가 메모
Summary by CodeRabbit
신규 기능
개선 사항