Files
awoooi/apps/web/src/components/panels/ErrorsPanel.tsx
Your Name 9e15fd08b3
All checks were successful
CD Pipeline / tests (push) Successful in 1m39s
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / build-and-deploy (push) Successful in 5m19s
CD Pipeline / post-deploy-checks (push) Successful in 2m11s
feat(web): land iwooos security posture surfaces
2026-05-25 20:35:52 +08:00

61 lines
3.0 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 { IwoooSReadOnlyBridge } from '@/components/security/iwooos-read-only-bridge'
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', flexWrap: 'wrap', gap: 12, 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>
<IwoooSReadOnlyBridge />
{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>
)
}