このコミットが含まれているのは:
2026-10-26 12:19:43 +09:00
コミット 3e8f209542
31個のファイルの変更、1676行の追加、394行の削除
+82 -46
ファイルの表示
@@ -1,62 +1,98 @@
import LocalisedText from '@/components/LocalisedText'
import PrefetchLink from '@/components/PrefetchLink'
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
import CountsSortHeader from '@/components/counts/CountsSortHeader'
import { changeCountSort, countsRequestParams, readCountSort, writeCountSort } from '@/lib/counts'
import { countsSummaryCell, countsSummaryDrill, countsSummaryRows,
selectedCountsSummaryFields } from '@/lib/countsState'
import { useLocale } from '@/lib/useLocale'
import { countsFieldLabel, readCountsJSON } from '@/lib/countsDefinitions'
import { useUITranslation } from '@/lib/useUITranslation'
import type { CountsResource, CountsResult } from '@/lib/counts'
import type { CountsAxis } from '@/lib/countsDefinitions'
const CountsSummaryResults = (
{ resource, data, params, pending, update }: {
resource: CountsResource
data: CountsResult
params: URLSearchParams
pending: boolean
update: (key: string, value: string) => void },
resource: CountsResource; data: CountsResult; params: URLSearchParams
pending: boolean; update: (key: string, value: string) => void },
) => {
const locale = useLocale ()
const { label, message } = useUITranslation ('counts')
const fields = selectedCountsSummaryFields (resource, params)
const rows = countsSummaryRows (data)
const sorts = readCountSort (params.get ('sort') ?? (
params.get ('axis') === 'tags' ? 'count:desc' : 'group:asc'))
return (
<div className="min-h-0 flex-1 overflow-auto" data-counts-results aria-busy={pending}>
{fields.length === 0 ? <p>{label ('nested.noColumns')}</p> : (
<CountsSelectableTable family={`summary_${params.get ('axis')}`}
columns={fields.map (field => field.key)}
identity={`${countsRequestParams (params)}:${fields.map (field => field.key)}`}
className="text-left text-sm">
<thead className="sticky top-0 z-20"><tr>
{fields.map (field => (
<CountsSortHeader key={field.key} field={field.key}
label={message (field.label)}
sorts={sorts} change={(key, additive) => update (
'sort', writeCountSort (changeCountSort (sorts, key, additive)))}/>))}
</tr></thead>
<tbody>{rows.map (row => (
<tr key={String (row.key)}>
{fields.map (field => {
const value = countsSummaryCell (row, field.key, locale)
const content = value.language == null ? value.text : (
<LocalisedText language={value.language}>{value.text}</LocalisedText>)
return <td key={field.key} data-counts-copy={value.text}
className="break-words border-b px-2 py-1 dark:border-slate-700">
{(['count', 'group'].includes (field.key) && row.count != null)
? <PrefetchLink to={countsSummaryDrill (resource, params, row)}>
{content}</PrefetchLink>
: content}
</td>
})}
</tr>))}</tbody>
</CountsSelectableTable>)}
{rows.length === 0 && <p className="p-2">{label ('empty')}</p>}
</div>)
const rows = data.rows ?? []
const cols = data.cols ?? []
const axes = [...rows, ...cols]
const cells = data.cells ?? []
const rowKeys = [...new Set (cells.map (cell => JSON.stringify (cell.values.slice (0, rows.length))))]
const columnKeys = data.columnValues?.map (values => JSON.stringify (values))
?? [...new Set (cells.map (cell => JSON.stringify (cell.values.slice (rows.length))))]
if (cols.length === 0 && columnKeys.length === 0) columnKeys.push ('[]')
const sorts = readCountSort (params.get ('sort'))
const display = (value: string | number | null, axis: CountsAxis, index: number): string => {
if (value == null) return ''
const logical = data.axisLabels?.[`a${index}`]?.[String (value)]
if (logical != null) return logical
if (['tags', 'user', 'language_code', 'source_language_code'].includes (axis.field))
return message ('notRegistered').text
if (axis.field === 'changed_field') return message (countsFieldLabel (String (value))).text
if (axis.field === 'event_type') return message (`events.${value}`).text
if (axis.field === 'category' || axis.field === 'tag_category')
return message (`common:categories.${value}`).text
return String (value)
}
const drill = (values: (string | number | null)[]) => {
const next = new URLSearchParams (params)
const existing = readCountsJSON<unknown[]> (params, 'drill', [])
next.set ('drill', JSON.stringify ([...existing,
...axes.map ((axis, index) => ({ ...axis, value: values[index] }))]))
next.set ('mode', 'details')
next.delete ('sort')
next.delete ('page')
return `/counts/${resource}?${next}`
}
const keys = [...rows.map ((_, index) => `a${index}`),
...columnKeys.map ((_, index) => `cell${index}`)]
return <div className="min-h-0 flex-1 overflow-auto" data-counts-results aria-busy={pending}>
<CountsSelectableTable family={`pivot_${resource}`} columns={keys}
identity={`${countsRequestParams (params)}:${keys}`} className="text-left text-sm">
<thead className="sticky top-0 z-20"><tr>
{rows.map ((axis, index) => <CountsSortHeader key={axis.field} field={`a${index}`}
label={message (countsFieldLabel (axis.field))} sorts={sorts}
change={(key, additive) => update ('sort', writeCountSort (
changeCountSort (sorts, key, additive)))}/>)}
{columnKeys.map ((key, index) => {
const values = JSON.parse (key) as (string | number | null)[]
const name = values.map ((value, position) =>
`${message (countsFieldLabel (cols[position].field)).text}: ${
display (value, cols[position], rows.length + position)}`).join (' / ')
const sortKey = cols.length === 0 ? 'count' : `measure_${[
...new TextEncoder ().encode (key)].map (byte =>
byte.toString (16).padStart (2, '0')).join ('')}`
return <CountsSortHeader key={key} field={`cell${index}`}
label={{ text: name || message ('count').text, language: message ('count').language }}
sorts={sorts.map (sort => sort.key === sortKey
? { ...sort, key: `cell${index}` } : sort)}
change={(_, additive) => update ('sort', writeCountSort (
changeCountSort (sorts, sortKey, additive)))}/>
})}
</tr></thead>
<tbody>{rowKeys.map (rowKey => {
const rowValues = JSON.parse (rowKey) as (string | number | null)[]
return <tr key={rowKey}>
{rowValues.map ((value, index) => <td key={index}
className="overflow-hidden break-words border px-2 py-1 dark:border-slate-700"
data-counts-copy={display (value, rows[index], index)}>
{display (value, rows[index], index)}</td>)}
{columnKeys.map (columnKey => {
const columnValues = JSON.parse (columnKey) as (string | number | null)[]
const values = [...rowValues, ...columnValues]
const cell = cells.find (cell => JSON.stringify (cell.values) === JSON.stringify (values))
return <td key={columnKey} data-counts-copy={String (cell?.count ?? 0)}
className="border px-2 py-1 dark:border-slate-700">
<PrefetchLink to={drill (values)}>{cell?.count ?? 0}</PrefetchLink>
</td>
})}
</tr>
})}</tbody>
</CountsSelectableTable>
{cells.length === 0 && <p>{label ('empty')}</p>}
</div>
}