fix: resolve 5 bugs on app overview page

- Migrate useQuery to useSWR for consistency (no mixed fetching)
- Fix trend badge: guard against undefined grid and NaN comparison
- Fix trend badge: hide when increase is exactly 0
- Fix version modal: use gridRef so background refetch cannot overwrite user edits
- ErrorDataTable: migrate to useSWR, expose refresh() via forwardRef so the
  refresh button at the top also reloads the error table
This commit is contained in:
2026-05-22 12:15:42 +08:00
parent 7808de0db3
commit 91dead0082
2 changed files with 36 additions and 24 deletions

View File

@@ -18,11 +18,15 @@ import {
Tooltip,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { useQuery } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
import dayjs from 'dayjs'
import { useState } from 'react'
import { forwardRef, useImperativeHandle, useState } from 'react'
import { TbBug, TbExternalLink, TbHistory, TbMessageReport } from 'react-icons/tb'
import useSWR from 'swr'
export interface ErrorDataTableHandle {
refresh: () => void
}
export interface ErrorDataTableProps {
appId?: string
@@ -45,15 +49,20 @@ const STATUS_LABEL: Record<string, string> = {
CLOSED: 'Closed',
}
export function ErrorDataTable({ appId }: ErrorDataTableProps) {
const fetcher = (url: string) => fetch(url).then((r) => r.json())
export const ErrorDataTable = forwardRef<ErrorDataTableHandle, ErrorDataTableProps>(
function ErrorDataTable({ appId }, ref) {
const [opened, { open, close }] = useDisclosure(false)
const [selectedError, setSelectedError] = useState<any>(null)
const [showStackTrace, setShowStackTrace] = useState(false)
const { data: bugsData, isLoading } = useQuery({
queryKey: ['bugs', appId],
queryFn: () => fetch(`/api/bugs?app=${appId || 'all'}&limit=10`).then((r) => r.json()),
})
const { data: bugsData, isLoading, mutate } = useSWR(
`/api/bugs?app=${appId || 'all'}&limit=10`,
fetcher
)
useImperativeHandle(ref, () => ({ refresh: mutate }))
const bugs = bugsData?.data || []
@@ -257,4 +266,4 @@ export function ErrorDataTable({ appId }: ErrorDataTableProps) {
</Drawer>
</>
)
}
})

View File

@@ -1,6 +1,5 @@
import { useQuery } from '@tanstack/react-query'
import { VillageActivityLineChart, VillageComparisonBarChart } from '@/frontend/components/DashboardCharts'
import { ErrorDataTable } from '@/frontend/components/ErrorDataTable'
import { ErrorDataTable, type ErrorDataTableHandle } from '@/frontend/components/ErrorDataTable'
import { SummaryCard } from '@/frontend/components/SummaryCard'
import { useSession } from '@/frontend/hooks/useAuth'
import {
@@ -21,7 +20,7 @@ import {
import { useDisclosure } from '@mantine/hooks'
import { notifications } from '@mantine/notifications'
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import {
TbActivity,
TbAlertTriangle,
@@ -45,6 +44,7 @@ function AppOverviewPage() {
const [versionModalOpened, { open: openVersionModal, close: closeVersionModal }] = useDisclosure(false)
const { data: session } = useSession()
const isDeveloper = session?.user?.role === 'DEVELOPER'
const errorTableRef = useRef<ErrorDataTableHandle>(null)
const [latestVersion, setLatestVersion] = useState('')
const [minVersion, setMinVersion] = useState('')
@@ -56,28 +56,31 @@ function AppOverviewPage() {
const { data: dailyRes, isLoading: dailyLoading, mutate: mutateDaily } = useSWR(isDesaPlus ? API_URLS.getDailyActivity() : null, fetcher)
const { data: comparisonRes, isLoading: comparisonLoading, mutate: mutateComparison } = useSWR(isDesaPlus ? API_URLS.getComparisonActivity() : null, fetcher)
const { data: appData, isLoading: appLoading } = useQuery({
queryKey: ['apps', appId],
queryFn: () => fetch(`/api/apps/${appId}`).then((r) => r.json()),
})
const { data: appData, isLoading: appLoading } = useSWR(`/api/apps/${appId}`, fetcher)
const grid = gridRes?.data
const dailyData = dailyRes?.data || []
const comparisonData = comparisonRes?.data || []
// Ref so the modal-sync effect always reads current grid without re-running on every background refetch
const gridRef = useRef(grid)
gridRef.current = grid
useEffect(() => {
if (grid?.version && versionModalOpened) {
setLatestVersion(grid.version.mobile_latest_version || '')
setMinVersion(grid.version.mobile_minimum_version || '')
setMessageUpdate(grid.version.mobile_message_update || '')
setMaintenance(grid.version.mobile_maintenance === 'true')
if (versionModalOpened && gridRef.current?.version) {
const v = gridRef.current.version
setLatestVersion(v.mobile_latest_version || '')
setMinVersion(v.mobile_minimum_version || '')
setMessageUpdate(v.mobile_message_update || '')
setMaintenance(v.mobile_maintenance === 'true')
}
}, [grid, versionModalOpened])
}, [versionModalOpened])
const handleRefresh = () => {
mutateGrid()
mutateDaily()
mutateComparison()
errorTableRef.current?.refresh()
}
const handleSaveVersion = async () => {
@@ -214,8 +217,8 @@ function AppOverviewPage() {
value={gridLoading ? '...' : (grid?.activity?.today?.toLocaleString() ?? '0')}
icon={TbActivity}
color="teal"
trend={grid?.activity?.increase
? { value: `${grid.activity.increase}%`, positive: grid.activity.increase > 0 }
trend={grid?.activity?.increase != null && Number(grid.activity.increase) !== 0
? { value: `${grid.activity.increase}%`, positive: Number(grid.activity.increase) > 0 }
: undefined}
/>
@@ -254,7 +257,7 @@ function AppOverviewPage() {
<VillageComparisonBarChart data={comparisonData} isLoading={comparisonLoading} />
</SimpleGrid>
<ErrorDataTable appId={appId} />
<ErrorDataTable ref={errorTableRef} appId={appId} />
</Stack>
</>
)