diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index 4a5d07dc..9de0c14f 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -212,7 +212,7 @@ export function AutomationInventoryTab() { const fetchSnapshot = () => { setLoading(true) - Promise.all([ + const requests = [ apiClient.getAiAgentAutomationInventorySnapshot(), apiClient.getAiAgentAutomationBacklogSnapshot(), apiClient.getBackupDrTargetInventory(), @@ -220,16 +220,35 @@ export function AutomationInventoryTab() { apiClient.getBackupNotificationPolicy(), apiClient.getOffsiteEscrowReadinessStatus(), apiClient.getRuntimeSurfaceInventory(), - ]) - .then(([inventoryData, backlogData, targetData, readinessData, policyData, offsiteEscrowData, runtimeSurfaceData]) => { - setSnapshot(inventoryData) - setBacklog(backlogData) - setBackupTargets(targetData) - setBackupReadiness(readinessData) - setBackupPolicy(policyData) - setOffsiteEscrow(offsiteEscrowData) - setRuntimeSurface(runtimeSurfaceData) - setError(false) + ] as const + + Promise.allSettled(requests) + .then((results) => { + const [ + inventoryResult, + backlogResult, + targetResult, + readinessResult, + policyResult, + offsiteEscrowResult, + runtimeSurfaceResult, + ] = results + + setSnapshot(inventoryResult.status === 'fulfilled' ? inventoryResult.value : null) + setBacklog(backlogResult.status === 'fulfilled' ? backlogResult.value : null) + setBackupTargets(targetResult.status === 'fulfilled' ? targetResult.value : null) + setBackupReadiness(readinessResult.status === 'fulfilled' ? readinessResult.value : null) + setBackupPolicy(policyResult.status === 'fulfilled' ? policyResult.value : null) + setOffsiteEscrow(offsiteEscrowResult.status === 'fulfilled' ? offsiteEscrowResult.value : null) + setRuntimeSurface(runtimeSurfaceResult.status === 'fulfilled' ? runtimeSurfaceResult.value : null) + setError([ + inventoryResult, + backlogResult, + targetResult, + readinessResult, + policyResult, + offsiteEscrowResult, + ].some(result => result.status === 'rejected')) }) .catch(() => setError(true)) .finally(() => setLoading(false)) @@ -327,7 +346,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !runtimeSurface) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow) { return (
@@ -374,10 +393,10 @@ export function AutomationInventoryTab() { const actionRequiredOffsiteCards = offsiteEscrow.rollups.by_readiness.action_required ?? 0 const blockedEscrowCards = offsiteEscrow.rollups.by_readiness.blocked ?? 0 const executionBlockedCards = offsiteEscrow.rollups.execution_blocked_card_ids.length - const runtimeActionRequired = runtimeSurface.rollups.action_required_surface_ids.length - const runtimeSecrets = runtimeSurface.rollups.secret_surface_ids.length - const runtimeLiveMissing = runtimeSurface.rollups.live_check_missing_surface_ids.length - const runtimeBoundComponents = runtimeSurface.rollups.source_components_with_runtime_binding + const runtimeActionRequired = runtimeSurface?.rollups.action_required_surface_ids.length ?? 0 + const runtimeSecrets = runtimeSurface?.rollups.secret_surface_ids.length ?? 0 + const runtimeLiveMissing = runtimeSurface?.rollups.live_check_missing_surface_ids.length ?? 0 + const runtimeBoundComponents = runtimeSurface?.rollups.source_components_with_runtime_binding ?? 0 const backlogProgressPercent = backlog.progress_summary.overall_percent const explicitApprovalItemCount = backlog.item_approval_boundary_rollup.items_requiring_explicit_approval.length const taskBoundaryCount = snapshot.task_approval_boundary_rollup.total_tasks @@ -873,6 +892,7 @@ export function AutomationInventoryTab() {
+ {runtimeSurface && (
@@ -976,6 +996,7 @@ export function AutomationInventoryTab() {
+ )}
diff --git a/apps/web/src/components/layout/page-tabs.tsx b/apps/web/src/components/layout/page-tabs.tsx index 36a9449e..611b16cc 100644 --- a/apps/web/src/components/layout/page-tabs.tsx +++ b/apps/web/src/components/layout/page-tabs.tsx @@ -101,12 +101,15 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps const [activeTab, setActiveTab] = useState(initialTab) const tabIds = tabs.map(t => t.id).join('|') const firstTabId = tabs[0]?.id || '' + const knownTabIds = useMemo(() => tabIds.split('|'), [tabIds]) + const urlActiveTab = syncWithUrl && urlTab && knownTabIds.includes(urlTab) ? urlTab : null + const resolvedActiveTab = urlActiveTab || activeTab || defaultTab || firstTabId useEffect(() => { const nextTab = (syncWithUrl ? urlTab : null) || defaultTab || firstTabId - if (!nextTab || !tabIds.split('|').includes(nextTab)) return + if (!nextTab || !knownTabIds.includes(nextTab)) return setActiveTab(prev => prev === nextTab ? prev : nextTab) - }, [defaultTab, firstTabId, syncWithUrl, tabIds, urlTab]) + }, [defaultTab, firstTabId, knownTabIds, syncWithUrl, urlTab]) // 切換 Tab const switchTab = useCallback((tabId: string) => { @@ -126,8 +129,8 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps // 找到目前的 Tab 內容 const activeContent = useMemo(() => { - return tabs.find(t => t.id === activeTab)?.content ?? tabs[0]?.content - }, [tabs, activeTab]) + return tabs.find(t => t.id === resolvedActiveTab)?.content ?? tabs[0]?.content + }, [tabs, resolvedActiveTab]) return ( <> @@ -147,17 +150,17 @@ export function PageTabs({ tabs, defaultTab, syncWithUrl = true }: PageTabsProps }} > {tabs.map(tab => { - const isActive = tab.id === activeTab + const isActive = tab.id === resolvedActiveTab return (