このコミットが含まれているのは:
2026-10-25 22:16:20 +09:00
コミット 96a5bb8601
12個のファイルの変更、600行の追加、25行の削除
+42 -9
ファイルの表示
@@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom'
import PrefetchLink from '@/components/PrefetchLink'
import CountsControls from '@/components/counts/CountsControls'
import CountsStateResults from '@/components/counts/CountsStateResults'
import CountsResults from '@/components/counts/CountsResults'
import CountsTableLayout from '@/components/counts/CountsTableLayout'
import PageHead from '@/components/common/PageHead'
@@ -13,8 +14,9 @@ import MainArea from '@/components/layout/MainArea'
import SidebarComponent from '@/components/layout/SidebarComponent'
import { Button } from '@/components/ui/button'
import { countsCSV, countsRequestParams, fieldsForCounts, fetchCounts,
normaliseCountsParams, selectedCountColumns } from '@/lib/counts'
isCountsState, normaliseCountsParams, selectedCountColumns } from '@/lib/counts'
import { countsCSVCell } from '@/lib/countsDisplay'
import { countsStateCell, countsStateDrill, countsStateFields } from '@/lib/countsState'
import { countsKeys } from '@/lib/queryKeys'
import { cn } from '@/lib/utils'
import { useLocale } from '@/lib/useLocale'
@@ -35,14 +37,16 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
const [sidebarOpen, setSidebarOpen] = useState (false)
const locale = useLocale ()
const { label, message } = useUITranslation ('counts')
const mode = params.get ('mode') ?? 'summary'
const mode = params.get ('mode') ?? 'state'
const state = isCountsState (params)
const stateTable = mode === 'state' || mode === 'trend'
const axis = params.get ('axis') ?? 'month'
const { data, isPending, isError, isPlaceholderData, refetch } = useQuery ({
queryKey: countsKeys.index (resource, request.toString (), locale),
queryFn: () => fetchCounts (resource, request, locale), retry: false,
placeholderData: (previous, query) => {
const previousMode = new URLSearchParams (String (query?.queryKey[3] ?? ''))
.get ('mode') ?? 'summary'
.get ('mode') ?? 'state'
return (query?.queryKey[1] === resource && query?.queryKey[2] === locale
&& previousMode === mode) ? previous : undefined
} })
@@ -52,6 +56,18 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
setParams (canonical, { replace: true })
}, [canonical, source, setParams])
useEffect (() => {
if (!state || data?.at == null || isPlaceholderData) return
const next = new URLSearchParams (canonical)
if (!next.get ('at')) next.set ('at', data.at)
if (mode === 'trend')
{
if (!next.get ('from') && data.from) next.set ('from', data.from)
if (!next.get ('before') && data.before) next.set ('before', data.before)
}
if (next.toString () !== canonical) setParams (next, { replace: true })
}, [canonical, data, isPlaceholderData, mode, setParams, state])
const update = (key: string, value: string) => {
const next = new URLSearchParams (params)
if (value || key === 'sort') next.set (key, value)
@@ -61,7 +77,12 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
}
const exportCSV = () => {
const columns = selectedCountColumns (resource, params)
const rows = mode === 'summary'
const stateFields = countsStateFields (mode === 'trend')
const rows = stateTable
? [stateFields.map (field => message (field.label).text),
...(data?.stateRows ?? []).map (row => stateFields.map (
field => countsStateCell (row, field.key, locale)))]
: (mode === 'summary'
? [[message (`axes.${axis}`).text, message ('metric').text],
...(data?.groups ?? []).map (group => [
group.key == null ? message ('history:bot').text : group.name ?? String (group.key),
@@ -69,7 +90,7 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
: [columns.map (column => message (
fieldsForCounts (resource).find (field => field.key === column)!.label).text),
...(data?.versions ?? []).map (row => columns.map (
column => countsCSVCell (row, column, resource, params, locale)))]
column => countsCSVCell (row, column, resource, params, locale)))])
const url = URL.createObjectURL (new Blob ([countsCSV (rows)], {
type: 'text/csv;charset=utf-8' }))
const link = document.createElement ('a')
@@ -123,11 +144,23 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
{isPending && <p>{label ('shared:messages.loadingEllipsis')}</p>}
{isError && <p role="alert">{label ('common:fetchError')}</p>}
{data != null && <>
<p className="my-1 text-xs">{label ('total', { count: data.count })}</p>
<CountsResults resource={resource} data={data} params={params}
pending={isPlaceholderData} update={update}/>
{state && data.at && <div className="my-1 flex flex-wrap gap-x-4 gap-y-1 text-xs">
<span>{label ('state.boundary')}:{data.at.replace ('T', ' ')}</span>
{data.totals && (['posts', 'tags', 'assignments'] as const).map (key => (
<span key={key}>{label (`state.totals.${key}`)}:
{key !== 'assignments' && data.totals![key] != null
? <PrefetchLink to={countsStateDrill (params, data.at!, key)}>
{data.totals![key]}</PrefetchLink>
: data.totals![key] ?? '—'}
</span>))}
</div>}
{!state && <p className="my-1 text-xs">{label ('total', { count: data.count })}</p>}
{stateTable ? <CountsStateResults data={data} params={params}
pending={isPlaceholderData} update={update}/> : (
<CountsResults resource={resource} data={data} params={params}
pending={isPlaceholderData} update={update}/>)}
<Pagination page={data.page} totalPages={Math.ceil (
(mode === 'summary' ? data.groupCount ?? 0 : data.count) / data.limit)}/>
((stateTable || mode === 'summary') ? data.groupCount ?? 0 : data.count) / data.limit)}/>
</>}
</MainArea>
</div>