Files
awoooi/apps/web/src/components/panels/ErrorsPanel.tsx
OG T 4a94588766
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
fix(web): I3 approve/reject API + I4 SIGNOZ_URL env + I5 ErrorsPanel nothing-gray
- I3: Approve/Reject 按鈕串接 /api/v1/approvals/{id}/sign|reject
- I4: ApmPanel SIGNOZ_URL 改用 NEXT_PUBLIC_SIGNOZ_URL 環境變數
- I5: ErrorsPanel 外框改用 nothing-gray 調色盤 inline style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 11:20:44 +08:00

59 lines
2.9 KiB
TypeScript

'use client'
/**
* ErrorsPanel — 錯誤追蹤面板 (不含 AppLayout)
* Sprint 5: 從 /errors/page.tsx 抽取
*/
import { useTranslations } from 'next-intl'
import { useErrors } from '@/hooks/useErrors'
import { useUXAudit } from '@/hooks/useUXAudit'
import {
ErrorOverviewCard,
RecentIssuesList,
ErrorTrendChart,
UXAuditCard,
} from '@/components/errors'
import { Bug, RefreshCw } from 'lucide-react'
import type { SentryIssue } from '@/lib/api-client'
export function ErrorsPanel() {
const t = useTranslations('errors')
const { stats, issues, trends, loading, error, activePeriod, refetch, setPeriod } = useErrors()
const { data: uxAuditData, loading: uxAuditLoading, error: uxAuditError } = useUXAudit()
const handleIssueClick = (issue: SentryIssue) => {
if (issue.permalink) window.open(issue.permalink, '_blank', 'noopener,noreferrer')
}
return (
<div style={{ padding: 24, maxWidth: 1280, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<Bug style={{ width: 24, height: 24, color: '#555550' }} />
<div>
<h1 style={{ fontSize: 18, fontWeight: 700, color: '#141413', margin: 0 }}>{t('title')}</h1>
<p style={{ fontSize: 12, color: '#87867f', margin: '4px 0 0' }}>{t('subtitle')}</p>
</div>
</div>
<button onClick={refetch} disabled={loading} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', fontSize: 13, background: '#f0efe8', border: '0.5px solid #e0ddd4', borderRadius: 6, cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.5 : 1 }}>
<RefreshCw className={`h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
{loading ? t('loading') : t('refresh')}
</button>
</div>
{error && <div style={{ marginBottom: 24, padding: 16, background: 'rgba(204,34,0,0.05)', border: '0.5px solid rgba(204,34,0,0.2)', borderRadius: 8 }}><p style={{ fontSize: 13, color: '#cc2200' }}>{error}</p></div>}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-1 space-y-6">
<ErrorOverviewCard stats={stats} loading={loading} changePercent={trends?.change_percent} />
<ErrorTrendChart data={trends} loading={loading} activePeriod={activePeriod} onPeriodChange={setPeriod} />
<UXAuditCard data={uxAuditData} loading={uxAuditLoading} error={uxAuditError} />
</div>
<div className="lg:col-span-2">
<RecentIssuesList issues={issues} loading={loading} onIssueClick={handleIssueClick} />
</div>
</div>
<div style={{ marginTop: 24, paddingTop: 16, borderTop: '0.5px solid #e0ddd4' }}><p style={{ fontSize: 11, color: '#b0ad9f', textAlign: 'center' }}>{t('footerInfo')}</p></div>
</div>
)
}