diff --git a/ui/src/pages/Records.jsx b/ui/src/pages/Records.jsx index e7cefad..b33ca55 100644 --- a/ui/src/pages/Records.jsx +++ b/ui/src/pages/Records.jsx @@ -1,16 +1,30 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { api } from '../api' +const DATE_RE = /^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/ + +function formatVal(val) { + if (val === null || val === undefined) return null + const s = String(val) + if (DATE_RE.test(s)) { + const d = new Date(s) + if (!isNaN(d)) return d.toLocaleDateString(undefined, { year: '2-digit', month: 'short', day: 'numeric' }) + } + return s +} + export default function Records({ source }) { const [rows, setRows] = useState([]) const [exists, setExists] = useState(null) const [offset, setOffset] = useState(0) const [loading, setLoading] = useState(false) + const [sort, setSort] = useState({ col: null, dir: 'asc' }) const LIMIT = 100 useEffect(() => { if (!source) return setOffset(0) + setSort({ col: null, dir: 'asc' }) load(0) }, [source]) @@ -27,11 +41,30 @@ export default function Records({ source }) { } } + function toggleSort(col) { + setSort(s => s.col === col + ? { col, dir: s.dir === 'asc' ? 'desc' : 'asc' } + : { col, dir: 'asc' } + ) + } + + const sorted = useMemo(() => { + if (!sort.col) return rows + return [...rows].sort((a, b) => { + const av = a[sort.col] ?? '' + const bv = b[sort.col] ?? '' + const cmp = String(av).localeCompare(String(bv), undefined, { numeric: true }) + return sort.dir === 'asc' ? cmp : -cmp + }) + }, [rows, sort]) + function prev() { const o = Math.max(0, offset - LIMIT); setOffset(o); load(o) } function next() { const o = offset + LIMIT; setOffset(o); load(o) } if (!source) return
| {col} | - ))} + {cols.map(col => { + const active = sort.col === col + return ( +toggleSort(col)} + className="px-3 py-2 font-medium whitespace-nowrap cursor-pointer select-none hover:text-gray-600" + > + {col} + + {active ? (sort.dir === 'asc' ? '▲' : '▼') : '⇅'} + + | + ) + })}
|---|---|
| - {val === null ? — : String(val)} - | - ))} + {cols.map((col, j) => { + const formatted = formatVal(row[col]) + return ( ++ {formatted === null ? — : formatted} + | + ) + })}