Files
awoooi/apps/web/src/components/shared/recent-activity.tsx
Your Name 83ae3619e8
All checks were successful
CD Pipeline / tests (push) Successful in 1m26s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 6m41s
CD Pipeline / post-deploy-checks (push) Successful in 2m14s
fix(web): wrap recent activity labels
2026-06-02 12:30:02 +08:00

87 lines
3.1 KiB
TypeScript

'use client'
/**
* RecentActivity — 最近活動時間線
* Sprint 5R S5: 設計稿 L416-429
* @created 2026-04-09 Claude Opus 4.6 Asia/Taipei
*/
import { useState, useEffect } from 'react'
import { useTranslations } from 'next-intl'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
interface LogEntry {
id: string
event_type: string
action_detail: string | null
actor: string | null
created_at: string
}
const EVENT_COLOR: Record<string, string> = {
RESOLVED: '#22C55E',
EXECUTION_COMPLETED: '#22C55E',
ALERT_RECEIVED: '#cc2200',
AUTO_REPAIR_TRIGGERED: '#4A90D9',
TELEGRAM_SENT: '#4A90D9',
EXECUTION_STARTED: '#F59E0B',
}
export function RecentActivity() {
const t = useTranslations('dashboard')
const [logs, setLogs] = useState<LogEntry[]>([])
useEffect(() => {
fetch(`${API_BASE}/api/v1/alert-operation-logs?limit=5`)
.then(r => r.ok ? r.json() : { items: [] })
.then(d => setLogs(d.items ?? []))
.catch(() => {})
}, [])
if (logs.length === 0) return null
return (
<div style={{
background: '#fff', border: '0.5px solid #e0ddd4', borderRadius: 10,
overflow: 'hidden', boxShadow: '0 1px 3px rgba(0,0,0,0.04)', flexShrink: 0,
}}>
<div style={{
padding: '10px 14px', borderBottom: '0.5px solid #e0ddd4',
display: 'flex', alignItems: 'center', gap: 8, background: '#faf9f3',
}}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#d97757' }} />
<span style={{ fontSize: 14, fontWeight: 700, color: '#141413', letterSpacing: '0.5px' }}>{t('activityStream')}</span>
<span style={{ marginLeft: 'auto', fontSize: 11, color: '#4A90D9', cursor: 'pointer', fontWeight: 500 }}>{t('viewAllAlerts')} </span>
</div>
<div style={{ padding: '10px 14px' }}>
{logs.map((log, i) => {
const time = (() => {
try {
return new Date(log.created_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' })
} catch { return '--' }
})()
const dotColor = EVENT_COLOR[log.event_type] ?? '#87867f'
const detail = log.action_detail || log.event_type.replace(/_/g, ' ').toLowerCase()
return (
<div key={log.id || i} style={{
display: 'flex', gap: 8, padding: '6px 0',
borderBottom: i < logs.length - 1 ? '0.5px solid #f0ede5' : 'none',
fontSize: 12,
}}>
<span style={{ fontSize: 10, color: '#87867f', fontFamily: "'JetBrains Mono', monospace", width: 40, flexShrink: 0 }}>{time}</span>
<span style={{ width: 4, height: 4, borderRadius: '50%', background: dotColor, marginTop: 5, flexShrink: 0 }} />
<span style={{ flex: 1, minWidth: 0, lineHeight: 1.4, color: '#555550', overflowWrap: 'anywhere', wordBreak: 'break-word' }}>
{log.actor && <b style={{ fontWeight: 600 }}>{log.actor}</b>}
{log.actor && ' · '}
{detail}
</span>
</div>
)
})}
</div>
</div>
)
}