このコミットが含まれているのは:
@@ -1,17 +1,17 @@
|
||||
import SortableColumns from '@/components/common/SortableColumns'
|
||||
import { countsSummaryValue } from '@/lib/countsDisplay'
|
||||
import PrefetchLink from '@/components/PrefetchLink'
|
||||
import CountsRowNumber from '@/components/counts/CountsRowNumber'
|
||||
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
||||
import CountsSortHeader from '@/components/counts/CountsSortHeader'
|
||||
import { changeCountSort, countsRequestParams, readCountSort, writeCountSort } from '@/lib/counts'
|
||||
import { changeCountSort, countsMaskedValue, countsRequestParams, readCountSort, writeCountSort }
|
||||
from '@/lib/counts'
|
||||
import { countsAxisLabel, useCountsDefinition }
|
||||
from '@/lib/countsDefinitions'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useLocale } from '@/lib/useLocale'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import { queryRecordParams, setQueryRecords, readCountState, encodeCountState, setCountState } from '@/lib/queryState'
|
||||
import { queryRecordParams, setQueryRecords, readCountState, encodeCountState, setCountState }
|
||||
from '@/lib/queryState'
|
||||
|
||||
import type { CountsPivotHeader, CountsResource, CountsResult } from '@/lib/counts'
|
||||
import type { CountsAxis } from '@/lib/countsDefinitions'
|
||||
@@ -21,12 +21,12 @@ const CountsSummaryTable = (
|
||||
{ resource, data, params, pending, update, dragIds = {} }: {
|
||||
dragIds?: Record<string, string>
|
||||
resource: CountsResource; data: CountsResult; params: URLSearchParams
|
||||
pending: boolean; update: (key: string, value: string, extra?: Record<string, string>) => void },
|
||||
pending: boolean
|
||||
update: (key: string, value: string, extra?: Record<string, string>) => void },
|
||||
) => {
|
||||
const definition = useCountsDefinition ()
|
||||
const locale = useLocale ()
|
||||
const { message } = useUITranslation ('counts')
|
||||
const numbered = false
|
||||
const rows = data.rows ?? []
|
||||
const cols = data.cols ?? []
|
||||
const cells = data.cells ?? []
|
||||
@@ -49,7 +49,8 @@ const CountsSummaryTable = (
|
||||
const headingLevels = cols.length === 0 ? 1 : levels * 2
|
||||
const names = (header: CountsPivotHeader, axes: CountsAxis[], offset: number) =>
|
||||
header.values.map ((value, index) => display (value, axes[index], offset + index))
|
||||
const columnKey = (header: CountsPivotHeader) => `${header.kind}:${JSON.stringify (header.values)}`
|
||||
const columnKey = (header: CountsPivotHeader) =>
|
||||
`${header.kind}:${JSON.stringify (header.values)}`
|
||||
const measures = readCountState<Record<string, string | number | null>[]> (
|
||||
params, 'measures', [])
|
||||
const measureKey = (header: CountsPivotHeader) => {
|
||||
@@ -66,13 +67,14 @@ const CountsSummaryTable = (
|
||||
const next = new URLSearchParams (params)
|
||||
const nextSort = changeCountSort (sorts, key, additive)
|
||||
const active = new Set (nextSort.map (sort => sort.key))
|
||||
const records = measures.filter (record => record.key !== key && active.has (String (record.key)))
|
||||
const records = measures.filter (record =>
|
||||
record.key !== key && active.has (String (record.key)))
|
||||
setQueryRecords (next, 'measures', [...records, ...(active.has (key)
|
||||
? header.values.map ((value, axis) => ({ key, axis, value })) : [])])
|
||||
update ('sort', writeCountSort (nextSort),
|
||||
queryRecordParams (next, 'measures'))
|
||||
}
|
||||
const keys = [...(numbered ? ['row_number'] : []),
|
||||
const keys = [
|
||||
...Array.from ({ length: leading }, (_, index) => `a${index}`),
|
||||
...columnHeaders.map (columnKey)]
|
||||
const full = new Map (cells.map (cell => [JSON.stringify (cell.values), cell.count]))
|
||||
@@ -86,22 +88,21 @@ const CountsSummaryTable = (
|
||||
: totals.get (JSON.stringify ([row.values, column.values])) ?? 0
|
||||
const drill = (row: CountsPivotHeader, column: CountsPivotHeader) => {
|
||||
const next = new URLSearchParams (params)
|
||||
const existing = readCountState<Record<string, string | number | null>[]> (params, 'conditions', [])
|
||||
const existing = readCountState<Record<string, string | number | null>[]> (
|
||||
params, 'conditions', [])
|
||||
setCountState (next, 'conditions', encodeCountState ('conditions', [...existing,
|
||||
...row.values.map ((value, index) => ({ ...rows[index], operator: 'axis_eq', value })),
|
||||
...column.values.map ((value, index) => ({ ...cols[index], operator: 'axis_eq', value }))]))
|
||||
...column.values.map ((value, index) => ({
|
||||
...cols[index], operator: 'axis_eq', value }))]))
|
||||
next.set ('mode', 'details')
|
||||
next.delete ('sort')
|
||||
next.delete ('page')
|
||||
return `/counts/${resource}?${next}`
|
||||
}
|
||||
let ordinal = (data.page - 1) * data.limit
|
||||
const rowLabels = rowHeaders.map (header => {
|
||||
const labels = names (header, rows, 0)
|
||||
// With no row axes the sole result row is not an additional rollup row.
|
||||
const number = rows.length === 0 ? '1' : (header.kind === 'data' ? String (++ordinal) : '')
|
||||
if (header.kind !== 'data') labels.push (aggregateLabel (header))
|
||||
return [...(numbered ? [number] : []),
|
||||
return [
|
||||
...Array.from ({ length: leading }, (_, index) => labels[index] ?? '')]
|
||||
})
|
||||
const spans = rowHeaders.map ((row, index) => rows.map ((_, axis) => {
|
||||
@@ -121,11 +122,10 @@ const CountsSummaryTable = (
|
||||
return span
|
||||
}))
|
||||
rowLabels.forEach ((labels, row) => spans[row].forEach ((span, column) => {
|
||||
if (span === 0) labels[column + (numbered ? 1 : 0)] = ''
|
||||
if (span === 0) labels[column] = ''
|
||||
}))
|
||||
// This unmerged matrix is also the single source for TSV and HTML clipboard headings.
|
||||
const valueHeadingRows = Array.from ({ length: levels }, (_, level) => [
|
||||
...(numbered ? [level === 0 ? message ('rowNumberHeading').text : ''] : []),
|
||||
...Array.from ({ length: leading }, (_, index) =>
|
||||
level === 0 && rows[index] != null
|
||||
? countsAxisLabel (rows[index], definition, locale).text : ''),
|
||||
@@ -142,7 +142,7 @@ const CountsSummaryTable = (
|
||||
return display (header.values[level], cols[level], rows.length + level)
|
||||
})])
|
||||
const axisHeadingRows = cols.map ((axis, level) => [
|
||||
...(numbered ? [''] : []), ...Array.from ({ length: leading }, () => ''),
|
||||
...Array.from ({ length: leading }, () => ''),
|
||||
...columnHeaders.map ((header, index) => {
|
||||
if (header.values.length < level) return ''
|
||||
const previous = columnHeaders[index - 1]
|
||||
@@ -155,7 +155,7 @@ const CountsSummaryTable = (
|
||||
: axisHeadingRows.flatMap ((headings, level) => [headings, valueHeadingRows[level]])
|
||||
if (cols.length > 0)
|
||||
{
|
||||
for (let index = 0; index < leading + (numbered ? 1 : 0); ++index)
|
||||
for (let index = 0; index < leading; ++index)
|
||||
{
|
||||
headingRows[0][index] = valueHeadingRows[0][index]
|
||||
headingRows[1][index] = ''
|
||||
@@ -214,6 +214,7 @@ const CountsSummaryTable = (
|
||||
const field = leaf ? columnKey (header) : sortKey
|
||||
output.push (<CountsSortHeader key={`${level}:${field}:${index}`} field={field}
|
||||
colSpan={span} resizable={leaf}
|
||||
disabled={header.values.includes (countsMaskedValue)}
|
||||
rowSpan={(aggregate && cols.length > 0) ? (levels - level) * 2 - 1 : 1}
|
||||
totalKind={header.kind !== 'data' ? header.kind : undefined}
|
||||
dragId={level === 0 && header.values.length > 0
|
||||
@@ -235,7 +236,6 @@ const CountsSummaryTable = (
|
||||
<thead className="sticky top-0 z-20">
|
||||
{headingRows.map ((_, tier) => <tr key={tier}>
|
||||
{tier === 0 && <>
|
||||
{numbered && <CountsRowNumber rowSpan={headingLevels}/>}
|
||||
{Array.from ({ length: leading }, (_, index) => rows[index] == null
|
||||
? <th key={index} rowSpan={headingLevels}/>
|
||||
: <CountsSortHeader key={rows[index].field} field={`a${index}`}
|
||||
@@ -249,7 +249,7 @@ const CountsSummaryTable = (
|
||||
</thead>
|
||||
<tbody>{rowHeaders.map ((row, index) => <tr key={columnKey (row)}>
|
||||
{rowLabels[index].map ((value, offset) => {
|
||||
const axis = offset - (numbered ? 1 : 0)
|
||||
const axis = offset
|
||||
if (axis >= 0 && row.kind !== 'data' && axis > row.values.length) return null
|
||||
const aggregate = axis >= 0 && row.kind !== 'data' && axis === row.values.length
|
||||
const span = axis < 0 ? 1 : spans[index][axis] ?? 1
|
||||
@@ -259,8 +259,8 @@ const CountsSummaryTable = (
|
||||
data-counts-aggregate={row.kind !== 'data' ? '' : undefined}
|
||||
data-counts-total={row.kind !== 'data' ? row.kind : undefined}
|
||||
data-counts-row-total={row.kind !== 'data' ? row.kind : undefined}
|
||||
className={cn ('overflow-hidden break-words border px-2 py-1 dark:border-slate-700',
|
||||
numbered && offset === 0 && 'text-right tabular-nums')}>{value === '********' ? <span
|
||||
className="overflow-hidden break-words border px-2 py-1 dark:border-slate-700">
|
||||
{value === '********' ? <span
|
||||
className="text-red-600 dark:text-red-400"
|
||||
title={message ('maskedReason').text}>{value}</span> : value}</td>
|
||||
})}
|
||||
@@ -273,8 +273,9 @@ const CountsSummaryTable = (
|
||||
data-counts-aggregate={(row.kind !== 'data' || column.kind !== 'data')
|
||||
? '' : undefined}
|
||||
className="border px-2 py-1 dark:border-slate-700">
|
||||
{[...row.values, ...column.values].includes ('__counts_private__')
|
||||
? <span>{count (row, column)}</span> : <PrefetchLink to={drill (row, column)} aria-label={
|
||||
{[...row.values, ...column.values].includes (countsMaskedValue)
|
||||
? <span>{count (row, column)}</span>
|
||||
: <PrefetchLink to={drill (row, column)} aria-label={
|
||||
totalLabels.get (JSON.stringify ([row.values, column.values])) == null ? undefined
|
||||
: `${message (totalLabels.get (
|
||||
JSON.stringify ([row.values, column.values]))!).text}: ${count (row, column)}`}>
|
||||
@@ -291,7 +292,8 @@ const CountsSummaryResults = (props: Parameters<typeof CountsSummaryTable>[0]) =
|
||||
const { message } = useUITranslation ('counts')
|
||||
const headers = props.data.columnHeaders ?? []
|
||||
const columns = props.data.cols ?? []
|
||||
if (columns.length === 0) return <CountsSummaryTable {...props}/>
|
||||
if (columns.length === 0 || props.data.columnValuesEditable === false)
|
||||
return <CountsSummaryTable {...props}/>
|
||||
const groups = [...new Set (headers.filter (header => header.values.length > 0)
|
||||
.map (header => JSON.stringify (header.values.slice (0, 1))))]
|
||||
const ids = groups.map ((_, index) => `group${index}`)
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする