Files
awoooi/apps/web/src/components/dashboard/connection-status.tsx
OG T 4d46e6b9a7
Some checks failed
E2E Health Check / e2e-health (push) Successful in 17s
CD Pipeline / build-and-deploy (push) Has been cancelled
style(web): 全站 font-mono → font-body (DM Mono 設計系統套用)
45 個 component + 6 個 page 統一從舊 font-mono 遷移到
font-body (DM Mono),確保設計系統一致性。

font-body = DM Mono (等寬),視覺效果相同但走新設計 token。
保留: font-heading (Syne)、font-dot-matrix (VT323/DSEG7)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 09:37:03 +08:00

136 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* ConnectionStatus - SSE 連線狀態指示器
* =====================================
* 顯示與後端 SSE 的連線狀態
*
* i18n: 100% 使用 useTranslations禁止任何寫死字串
* 符合 AWOOOI 專案開發憲法 v2.0
*
* States:
* - disconnected: 灰色
* - connecting: 藍色 pulse
* - connected: 綠色
* - reconnecting: 橘色 pulse
* - error: 紅色
*/
import { useTranslations } from 'next-intl'
import { useLocale } from 'next-intl'
import { StatusOrb, type StatusType } from '@/components/ui/status-orb'
import { useConnectionStatus, useMockMode, useLastUpdate } from '@/stores/dashboard.store'
import { cn } from '@/lib/utils'
// =============================================================================
// Status Mapping
// =============================================================================
type ConnectionStatusType = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'
const connectionToStatus: Record<ConnectionStatusType, StatusType> = {
disconnected: 'idle',
connecting: 'syncing',
connected: 'healthy',
reconnecting: 'warning',
error: 'critical',
}
// i18n key mapping (不再使用 hardcoded labels)
const connectionLabelKeys: Record<ConnectionStatusType, string> = {
disconnected: 'disconnected',
connecting: 'connecting',
connected: 'connected',
reconnecting: 'reconnecting',
error: 'error',
}
// =============================================================================
// Component
// =============================================================================
interface ConnectionStatusProps {
className?: string
showLabel?: boolean
showLastUpdate?: boolean
}
export function ConnectionStatus({
className,
showLabel = true,
showLastUpdate = false,
}: ConnectionStatusProps) {
const t = useTranslations('connection')
const _tCommon = useTranslations('common')
const locale = useLocale()
const connectionStatus = useConnectionStatus()
const mockMode = useMockMode()
const lastUpdate = useLastUpdate()
const status = connectionToStatus[connectionStatus]
const labelKey = connectionLabelKeys[connectionStatus]
// Format last update time with dynamic locale
const lastUpdateStr = lastUpdate
? lastUpdate.toLocaleTimeString(locale === 'zh-TW' ? 'zh-TW' : 'en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
: '--:--:--'
return (
<div className={cn('flex items-center gap-2', className)}>
<StatusOrb
status={status}
size="sm"
pulse={connectionStatus === 'connecting' || connectionStatus === 'reconnecting'}
glow={connectionStatus === 'connected'}
/>
{showLabel && (
<div className="flex flex-col">
<span className="text-xs font-body text-nothing-gray-600">
{t(labelKey)}
</span>
{showLastUpdate && lastUpdate && (
<span className="text-[10px] font-body text-nothing-gray-400">
{lastUpdateStr}
</span>
)}
</div>
)}
{mockMode && (
<span className="px-1.5 py-0.5 text-[10px] font-body bg-status-warning/10 text-status-warning rounded">
{t('mockMode')}
</span>
)}
</div>
)
}
// =============================================================================
// Compact Version (for header)
// =============================================================================
export function ConnectionStatusCompact({ className }: { className?: string }) {
const t = useTranslations('connection')
const connectionStatus = useConnectionStatus()
const status = connectionToStatus[connectionStatus]
return (
<div
className={cn(
'flex items-center gap-2 px-3 py-1.5 rounded-lg bg-nothing-gray-100',
className
)}
>
<StatusOrb status={status} size="sm" pulse={connectionStatus !== 'connected'} />
<span className="font-body text-xs text-nothing-gray-600 uppercase">
{t(connectionLabelKeys[connectionStatus])}
</span>
</div>
)
}