fix(web): 穩定治理頁 deep link 與盤點容錯
This commit is contained in:
@@ -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 (
|
||||
<div style={{ padding: 20 }}>
|
||||
<GlassCard variant="subtle" padding="lg">
|
||||
@@ -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() {
|
||||
</div>
|
||||
</GlassCard>
|
||||
|
||||
{runtimeSurface && (
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
|
||||
@@ -976,6 +996,7 @@ export function AutomationInventoryTab() {
|
||||
</div>
|
||||
</div>
|
||||
</GlassCard>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 0.8fr)', gap: 12 }} className="automation-inventory-bottom-grid">
|
||||
<GlassCard variant="subtle" padding="md">
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
key={tab.id}
|
||||
data-tab-id={tab.id}
|
||||
onClick={() => switchTab(tab.id)}
|
||||
style={{
|
||||
padding: '0 14px',
|
||||
fontSize: 12,
|
||||
fontWeight: isActive ? 600 : 500,
|
||||
color: isActive ? '#d97757' : '#87867f',
|
||||
borderBottom: `2px solid ${isActive ? '#d97757' : 'transparent'}`,
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
borderBottomWidth: 2,
|
||||
|
||||
Reference in New Issue
Block a user