dataflow/ui/src/pages/Log.jsx
Paul Trowbridge 4eda1c48b7 Replace dark-mode overrides with semantic colour tokens
Dark mode was 58 `.dark .bg-white`-style rules patching over components
that hardcoded light shades, so every new component silently owed the
stylesheet another override — a debt this session kept adding to.

Components now name the role of a colour rather than the shade:
bg-surface, text-ink, text-muted, border-line, text-danger. Those map
through @theme to CSS variables, and light and dark are two sets of
values for the same variables. The override block is gone entirely.

Solid button fills stay literal; they read correctly on both themes and
never had overrides.

Verified in the browser in both themes, Perspective included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2HFeU5neCKagTnmA6o9Tu
2026-08-02 12:41:31 -04:00

101 lines
3.3 KiB
JavaScript

import { useState, useEffect } from 'react'
import { api } from '../api'
function KeyList({ keys, label, color }) {
if (!keys || keys.length === 0) return null
return (
<div className="mb-2">
<div className={`text-xs font-medium mb-1 ${color}`}>{label} ({keys.length})</div>
<div className="max-h-32 overflow-y-auto bg-raised rounded p-2 font-mono text-xs text-muted space-y-0.5">
{keys.map((k, i) => (
<div key={i}>
{typeof k === 'object' && k !== null
? Object.entries(k).map(([field, val]) => `${field}: ${val}`).join(' · ')
: k}
</div>
))}
</div>
</div>
)
}
function LogRow({ entry }) {
const [expanded, setExpanded] = useState(false)
const info = entry.info || {}
const insertedKeys = info.inserted_keys || []
const excludedKeys = info.excluded_keys || []
const hasKeys = insertedKeys.length > 0 || excludedKeys.length > 0
return (
<>
<tr className="border-b border-line-soft hover:bg-raised">
<td className="py-1.5 text-xs text-muted font-mono pr-3">{entry.id}</td>
<td className="py-1.5 text-ink-soft pr-3">{entry.source_name}</td>
<td className="py-1.5 text-muted pr-3">{new Date(entry.imported_at).toLocaleString()}</td>
<td className="py-1.5 text-ink pr-3">{entry.records_imported}</td>
<td className="py-1.5 text-muted pr-3">{entry.records_duplicate}</td>
<td className="py-1.5">
{hasKeys && (
<button
onClick={() => setExpanded(e => !e)}
className="text-xs text-accent hover:text-accent"
>
{expanded ? '▲ hide' : '▼ keys'}
</button>
)}
</td>
</tr>
{expanded && (
<tr className="bg-raised">
<td colSpan={6} className="px-4 py-3">
<KeyList keys={insertedKeys} label="Inserted" color="text-ok" />
<KeyList keys={excludedKeys} label="Excluded" color="text-muted" />
</td>
</tr>
)}
</>
)
}
export default function Log() {
const [log, setLog] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
api.getAllImportLog()
.then(setLog)
.catch(() => {})
.finally(() => setLoading(false))
}, [])
return (
<div className="p-6">
<h1 className="text-xl font-semibold text-ink mb-6">Import Log</h1>
{loading && <p className="text-sm text-muted">Loading</p>}
{!loading && log.length === 0 && (
<p className="text-sm text-muted">No imports yet.</p>
)}
{log.length > 0 && (
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-muted border-b border-line-soft">
<th className="pb-1 font-medium pr-3">ID</th>
<th className="pb-1 font-medium pr-3">Source</th>
<th className="pb-1 font-medium pr-3">Date</th>
<th className="pb-1 font-medium pr-3">Imported</th>
<th className="pb-1 font-medium pr-3">Duplicates</th>
<th className="pb-1 w-16"></th>
</tr>
</thead>
<tbody>
{log.map(entry => <LogRow key={entry.id} entry={entry} />)}
</tbody>
</table>
)}
</div>
)
}