このコミットが含まれているのは:
2026-10-27 01:02:53 +09:00
コミット 53ea010f82
7個のファイルの変更、160行の追加、94行の削除
+62 -17
ファイルの表示
@@ -45,6 +45,7 @@ const CountsSummaryTable = (
? 'singleTotal' : 'grandTotal') : 'subtotal').text
const leading = Math.max (rows.length, cols.length > 0 ? 1 : 0)
const levels = Math.max (1, cols.length)
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)}`
@@ -119,14 +120,13 @@ const CountsSummaryTable = (
if (span === 0) labels[column + (numbered ? 1 : 0)] = ''
}))
// This unmerged matrix is also the single source for TSV and HTML clipboard headings.
const headingRows = Array.from ({ length: levels }, (_, level) => [
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 : ''),
...columnHeaders.map ((header, index) => {
if (cols.length === 0) return header.kind === 'data'
? message ('count').text : aggregateLabel (header)
if (cols.length === 0) return message ('count').text
if (level > header.values.length) return ''
if (level === header.values.length)
return header.kind === 'data' ? '' : aggregateLabel (header)
@@ -137,6 +137,48 @@ const CountsSummaryTable = (
return ''
return display (header.values[level], cols[level], rows.length + level)
})])
const axisHeadingRows = cols.map ((axis, level) => [
...(numbered ? [''] : []), ...Array.from ({ length: leading }, () => ''),
...columnHeaders.map ((header, index) => {
if (header.values.length < level) return ''
const previous = columnHeaders[index - 1]
const prefix = JSON.stringify (header.values.slice (0, level))
return (previous != null && previous.values.length >= level
&& JSON.stringify (previous.values.slice (0, level)) === prefix)
? '' : countsAxisLabel (axis, definition, locale).text
})])
const headingRows = cols.length === 0 ? valueHeadingRows
: axisHeadingRows.flatMap ((headings, level) => [headings, valueHeadingRows[level]])
if (cols.length > 0)
{
for (let index = 0; index < leading + (numbered ? 1 : 0); ++index)
{
headingRows[0][index] = valueHeadingRows[0][index]
headingRows[1][index] = ''
}
}
const axisHeaderCells = (level: number) => {
const output = []
for (let index = 0; index < columnHeaders.length; ++index)
{
const header = columnHeaders[index]
if (header.values.length < level) continue
const prefix = JSON.stringify (header.values.slice (0, level))
let span = 1
while (index + span < columnHeaders.length)
{
const sibling = columnHeaders[index + span]
if (sibling.values.length < level
|| JSON.stringify (sibling.values.slice (0, level)) !== prefix) break
++span
}
const name = countsAxisLabel (cols[level], definition, locale)
output.push (<th key={index} colSpan={span} scope="colgroup"
lang={name.language} className="px-2 py-1">{name.text}</th>)
index += span - 1
}
return output
}
const matrix = rowHeaders.map ((header, index) => [...rowLabels[index],
...columnHeaders.map (column => String (count (header, column)))])
const headerCells = (level: number) => {
@@ -147,8 +189,10 @@ const CountsSummaryTable = (
if (cols.length > 0 && level > header.values.length) continue
const aggregate = header.kind !== 'data' && level === header.values.length
const leaf = level === levels - 1 || aggregate
const text = aggregate ? aggregateLabel (header)
: (cols.length === 0 ? message ('count').text
const text =
cols.length === 0
? message ('count').text
: (aggregate ? aggregateLabel (header)
: display (header.values[level], cols[level], rows.length + level))
let span = 1
if (!leaf)
@@ -165,7 +209,8 @@ const CountsSummaryTable = (
const sortKey = leaf ? measureKey (header) : `a${rows.length + level}`
const field = leaf ? columnKey (header) : sortKey
output.push (<CountsSortHeader key={`${level}:${field}:${index}`} field={field}
colSpan={span} rowSpan={aggregate ? levels - level : 1} resizable={leaf}
colSpan={span} resizable={leaf}
rowSpan={(aggregate && cols.length > 0) ? (levels - level) * 2 - 1 : 1}
totalKind={header.kind !== 'data' ? header.kind : undefined}
dragId={level === 0 && header.values.length > 0
? dragIds[JSON.stringify (header.values.slice (0, 1))] : undefined}
@@ -184,16 +229,18 @@ const CountsSummaryTable = (
identity={`${countsRequestParams (params)}:${JSON.stringify (keys)}`}
className="text-left text-sm">
<thead className="sticky top-0 z-20">
{headingRows.map ((_, level) => <tr key={level}>
{level === 0 && <>
{numbered && <CountsRowNumber rowSpan={levels}/>}
{headingRows.map ((_, tier) => <tr key={tier}>
{tier === 0 && <>
{numbered && <CountsRowNumber rowSpan={headingLevels}/>}
{Array.from ({ length: leading }, (_, index) => rows[index] == null
? <th key={index} rowSpan={levels}/>
? <th key={index} rowSpan={headingLevels}/>
: <CountsSortHeader key={rows[index].field} field={`a${index}`}
rowSpan={levels} label={countsAxisLabel (rows[index], definition, locale)}
rowSpan={headingLevels}
label={countsAxisLabel (rows[index], definition, locale)}
sorts={sorts} change={change}/>)}
</>}
{headerCells (level)}
{(cols.length > 0 && tier % 2 === 0)
? axisHeaderCells (tier / 2) : headerCells (Math.floor (tier / 2))}
</tr>)}
</thead>
<tbody>{rowHeaders.map ((row, index) => <tr key={columnKey (row)}>
@@ -204,21 +251,19 @@ const CountsSummaryTable = (
role={axis >= 0 ? 'rowheader' : undefined} data-counts-copy={value}
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}</td>
})}
{columnHeaders.map (column => <td key={columnKey (column)}
data-counts-copy={String (count (row, column))}
data-counts-row-total={row.kind !== 'data' ? row.kind : undefined}
data-counts-total={(row.kind === 'grand_total' || column.kind === 'grand_total')
? 'grand_total' : ((row.kind === 'subtotal' || column.kind === 'subtotal')
? 'subtotal' : undefined)}
data-counts-aggregate={(row.kind !== 'data' || column.kind !== 'data')
? '' : undefined}
className={cn ('border px-2 py-1 dark:border-slate-700',
(row.kind !== 'data' || column.kind !== 'data')
&& 'bg-slate-200 text-slate-950 font-semibold',
(row.kind !== 'data' || column.kind !== 'data')
&& 'dark:bg-slate-700 dark:text-slate-50')}>
className="border px-2 py-1 dark:border-slate-700">
<PrefetchLink to={drill (row, column)}>{count (row, column)}</PrefetchLink>
</td>)}
</tr>)}</tbody>