115 行
5.0 KiB
TypeScript
115 行
5.0 KiB
TypeScript
import LocalisedText from '@/components/LocalisedText'
|
|
import PrefetchLink from '@/components/PrefetchLink'
|
|
import CountsSortHeader from '@/components/counts/CountsSortHeader'
|
|
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
|
import { changeCountSort, readCountSort, selectedCountChildren,
|
|
writeCountSort, orderedCountFields } from '@/lib/counts'
|
|
import { countsLogicalName, countsNestedClipboardTable, countsNestedExport,
|
|
countsNestedOrder, countsNestedText, countsNestedIsScalar, countsNestedScalarText }
|
|
from '@/lib/countsDisplay'
|
|
import { useLocale } from '@/lib/useLocale'
|
|
import { useUITranslation } from '@/lib/useUITranslation'
|
|
|
|
import type { CountField, CountRecord, CountsResource } from '@/lib/counts'
|
|
|
|
|
|
const CountsNestedTable = (
|
|
{ rows, fields, path, resource, title, params, update }: {
|
|
rows: CountRecord[]
|
|
fields: CountField[]
|
|
path: string
|
|
resource: CountsResource
|
|
title: { text: string; language: string }
|
|
params: URLSearchParams
|
|
update: (key: string, value: string) => void },
|
|
) => {
|
|
const { message } = useUITranslation ('counts')
|
|
const locale = useLocale ()
|
|
const selected = selectedCountChildren (params)
|
|
const columns = orderedCountFields (fields, params, path)
|
|
.filter (field => selected.has (`${path}.${field.key}`))
|
|
const allSorts = readCountSort (params.get ('child_sort'))
|
|
const sorts = allSorts.filter (sort => sort.key.startsWith (`${path}/`))
|
|
.map (sort => ({ ...sort, key: sort.key.slice (path.length + 1) }))
|
|
const ordered = countsNestedOrder (rows, path, params, locale)
|
|
const scalar = (row: CountRecord, field: string) => {
|
|
const value = row[field]
|
|
if (value == null) return null
|
|
if (field === 'tagId' || field === 'tagGroupId')
|
|
return <PrefetchLink to={field === 'tagId'
|
|
? `/tags/${encodeURIComponent (String (value))}` : '/wiki'}>
|
|
{String (value)}</PrefetchLink>
|
|
if (field === 'id')
|
|
{
|
|
if (row.kind === 'external') return String (value)
|
|
const target = path === 'parents' ? resource : 'tags'
|
|
return <PrefetchLink to={`/${target}/${encodeURIComponent (String (value))}`}>
|
|
{String (value)}</PrefetchLink>
|
|
}
|
|
const display = countsLogicalName (row, field, locale)
|
|
if (typeof value === 'boolean')
|
|
return <span aria-label={display.text} lang={display.language}>{value ? '✓' : '✗'}</span>
|
|
if (display.language != null)
|
|
return <LocalisedText language={display.language}>{display.text}</LocalisedText>
|
|
if (field === 'name' && typeof row.languageCode === 'string')
|
|
return <LocalisedText language={row.languageCode}>{String (value)}</LocalisedText>
|
|
return display.text
|
|
}
|
|
if (countsNestedIsScalar (fields, path, params))
|
|
return <span>{countsNestedScalarText (rows, fields, path, params, locale)}</span>
|
|
return (
|
|
<div className="counts-nested w-full min-w-0 max-w-full">
|
|
{params.get ('nested_json') === '1' ? (
|
|
<pre data-counts-scroll
|
|
aria-label={title.text} lang={title.language}
|
|
className="max-h-72 w-full overflow-auto whitespace-pre text-xs">
|
|
{JSON.stringify (countsNestedExport (rows, fields, path, params, locale))}
|
|
</pre>) : (columns.length > 0 && rows.length > 0 && (
|
|
<div data-counts-scroll
|
|
className="relative isolate max-h-72 w-full min-w-0 overflow-auto">
|
|
<CountsSelectableTable family={path} columns={columns.map (field => field.key)}
|
|
identity={`${path}:${params}`} name={title}
|
|
copyTable={() => countsNestedClipboardTable (rows, fields, path, params, locale)}
|
|
className="text-xs">
|
|
<thead className="sticky top-0 z-10 bg-background text-foreground
|
|
dark:bg-background dark:text-foreground"><tr>
|
|
{columns.map (field => (
|
|
<CountsSortHeader key={field.key} field={field.key}
|
|
label={message (field.label)}
|
|
sorts={sorts} structured={field.children != null}
|
|
change={(key, additive) => {
|
|
const next = changeCountSort (sorts, key, additive)
|
|
.map (sort => ({ ...sort, key: `${path}/${sort.key}` }))
|
|
update ('child_sort', writeCountSort ([
|
|
...allSorts.filter (sort => !sort.key.startsWith (`${path}/`)),
|
|
...next]))
|
|
}}/>))}
|
|
</tr></thead>
|
|
<tbody>{ordered.map ((row, index) => (
|
|
<tr key={index} className="border-t dark:border-slate-700">
|
|
{columns.map (field => (
|
|
<td key={field.key}
|
|
className="overflow-hidden break-words border-t px-3 py-2
|
|
align-top dark:border-slate-700"
|
|
data-counts-copy={
|
|
field.children
|
|
? countsNestedText (
|
|
row[field.key] as CountRecord[] ?? [], field.children,
|
|
`${path}.${field.key}`, params, locale)
|
|
: countsLogicalName (row, field.key, locale).text}>
|
|
{field.children
|
|
? <CountsNestedTable rows={row[field.key] as CountRecord[] ?? []}
|
|
fields={field.children} path={`${path}.${field.key}`}
|
|
resource={resource}
|
|
title={message (field.label)} params={params} update={update}/>
|
|
: scalar (row, field.key)}
|
|
</td>))}
|
|
</tr>))}</tbody>
|
|
</CountsSelectableTable>
|
|
</div>))}
|
|
</div>)
|
|
}
|
|
|
|
|
|
export default CountsNestedTable
|