- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
138 lines
3.7 KiB
TypeScript
138 lines
3.7 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* useIncidents - Phase 7 事件訂閱 Hook
|
||
* =====================================
|
||
*
|
||
* 功能:
|
||
* - 從 /api/v1/incidents 獲取活躍事件
|
||
* - 從 /api/v1/approvals/pending 獲取待簽核提案
|
||
* - 支援輪詢更新 (Smart Polling)
|
||
*
|
||
* 統帥鐵律: 禁止假數據!所有資料必須來自真實 API
|
||
*/
|
||
|
||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||
import {
|
||
apiClient,
|
||
type IncidentResponse,
|
||
type ApprovalResponse,
|
||
} from '@/lib/api-client'
|
||
|
||
// =============================================================================
|
||
// Types
|
||
// =============================================================================
|
||
|
||
interface UseIncidentsOptions {
|
||
/** 輪詢間隔 (毫秒),0 則不輪詢 */
|
||
pollInterval?: number
|
||
/** 是否啟用輪詢 */
|
||
enablePolling?: boolean
|
||
}
|
||
|
||
interface UseIncidentsResult {
|
||
/** 活躍事件清單 */
|
||
incidents: IncidentResponse[]
|
||
/** 待簽核提案清單 */
|
||
pendingApprovals: ApprovalResponse[]
|
||
/** 載入中狀態 */
|
||
isLoading: boolean
|
||
/** 錯誤訊息 */
|
||
error: string | null
|
||
/** 手動刷新 */
|
||
refresh: () => Promise<void>
|
||
/** 最後更新時間 */
|
||
lastUpdated: Date | null
|
||
}
|
||
|
||
// =============================================================================
|
||
// Hook
|
||
// =============================================================================
|
||
|
||
export function useIncidents(options: UseIncidentsOptions = {}): UseIncidentsResult {
|
||
const { pollInterval = 15000, enablePolling = true } = options
|
||
|
||
const [incidents, setIncidents] = useState<IncidentResponse[]>([])
|
||
const [pendingApprovals, setPendingApprovals] = useState<ApprovalResponse[]>([])
|
||
const [isLoading, setIsLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [lastUpdated, setLastUpdated] = useState<Date | null>(null)
|
||
|
||
const mountedRef = useRef(true)
|
||
const pollTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||
|
||
const fetchData = useCallback(async () => {
|
||
try {
|
||
// 並行請求 incidents 和 pending approvals
|
||
const [incidentsRes, approvalsRes] = await Promise.all([
|
||
apiClient.listIncidents(),
|
||
apiClient.getPendingApprovals(),
|
||
])
|
||
|
||
if (!mountedRef.current) return
|
||
|
||
setIncidents(incidentsRes.incidents)
|
||
setPendingApprovals(approvalsRes.approvals)
|
||
setError(null)
|
||
setLastUpdated(new Date())
|
||
} catch (err) {
|
||
if (!mountedRef.current) return
|
||
|
||
const message = err instanceof Error ? err.message : 'Unknown error'
|
||
setError(message)
|
||
// eslint-disable-next-line no-console
|
||
console.error('[useIncidents] Fetch error:', message)
|
||
} finally {
|
||
if (mountedRef.current) {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
}, [])
|
||
|
||
const refresh = useCallback(async () => {
|
||
setIsLoading(true)
|
||
await fetchData()
|
||
}, [fetchData])
|
||
|
||
// Initial fetch and polling
|
||
useEffect(() => {
|
||
mountedRef.current = true
|
||
|
||
fetchData()
|
||
|
||
if (enablePolling && pollInterval > 0) {
|
||
const poll = () => {
|
||
pollTimeoutRef.current = setTimeout(async () => {
|
||
if (mountedRef.current) {
|
||
await fetchData()
|
||
poll()
|
||
}
|
||
}, pollInterval)
|
||
}
|
||
poll()
|
||
}
|
||
|
||
return () => {
|
||
mountedRef.current = false
|
||
if (pollTimeoutRef.current) {
|
||
clearTimeout(pollTimeoutRef.current)
|
||
}
|
||
}
|
||
}, [fetchData, enablePolling, pollInterval])
|
||
|
||
return {
|
||
incidents,
|
||
pendingApprovals,
|
||
isLoading,
|
||
error,
|
||
refresh,
|
||
lastUpdated,
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// Export
|
||
// =============================================================================
|
||
|
||
export type { UseIncidentsOptions, UseIncidentsResult }
|