Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
- 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>
59 lines
2.9 KiB
TypeScript
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>
|
|
)
|
|
}
|