Add a mobile layout and stop shipping Perspective to every page
Below md: the sidebar is replaced by a fixed bottom bar carrying the same destinations plus the theme toggle. NAV moved to navItems.jsx so the two can't drift apart. Perspective is now lazy-loaded. It was ~90% of the bundle and only Pivot uses it, so the initial download drops from 4.9 MB gzipped to 173 kB and the rest arrives only when a pivot is opened. Desktop benefits as much as phones do. Bridge's balance table becomes stacked cards under sm: with the same subtotals, source tabs scroll rather than wrap, and page gutters tighten on small screens. Rules, Mappings, and Records are deliberately untouched — they are wide data tables and belong on a desktop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2HFeU5neCKagTnmA6o9Tu
This commit is contained in:
parent
4eda1c48b7
commit
356e61d043
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ui</title>
|
<title>Dataflow</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { useState, useEffect, createElement } from 'react'
|
import { useState, useEffect, createElement, lazy, Suspense } from 'react'
|
||||||
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom'
|
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom'
|
||||||
import { api, setCredentials, clearCredentials } from './api'
|
import { api, setCredentials, clearCredentials } from './api'
|
||||||
import Sidebar from './components/Sidebar.jsx'
|
import Sidebar from './components/Sidebar.jsx'
|
||||||
|
import BottomNav from './components/BottomNav.jsx'
|
||||||
import SourceTabs from './components/SourceTabs.jsx'
|
import SourceTabs from './components/SourceTabs.jsx'
|
||||||
import Login from './pages/Login'
|
import Login from './pages/Login'
|
||||||
import SourceList from './pages/SourceList'
|
import SourceList from './pages/SourceList'
|
||||||
@ -13,7 +14,7 @@ import Rules from './pages/Rules'
|
|||||||
import Mappings from './pages/Mappings'
|
import Mappings from './pages/Mappings'
|
||||||
import Records from './pages/Records'
|
import Records from './pages/Records'
|
||||||
import Log from './pages/Log'
|
import Log from './pages/Log'
|
||||||
import Pivot from './pages/Pivot'
|
const Pivot = lazy(() => import('./pages/Pivot'))
|
||||||
import Remap from './pages/Remap'
|
import Remap from './pages/Remap'
|
||||||
import Stacks from './pages/Stacks'
|
import Stacks from './pages/Stacks'
|
||||||
|
|
||||||
@ -134,12 +135,14 @@ export default function App() {
|
|||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
|
|
||||||
|
<div className="hidden md:flex">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
expanded={sidebarExpanded}
|
expanded={sidebarExpanded}
|
||||||
setExpanded={setSidebarExpanded}
|
setExpanded={setSidebarExpanded}
|
||||||
loginUser={loginUser}
|
loginUser={loginUser}
|
||||||
onLogout={handleLogout}
|
onLogout={handleLogout}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main */}
|
{/* Main */}
|
||||||
<div className="flex-1 overflow-hidden flex flex-col min-w-0">
|
<div className="flex-1 overflow-hidden flex flex-col min-w-0">
|
||||||
@ -191,7 +194,8 @@ export default function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto pb-14 md:pb-0">
|
||||||
|
<Suspense fallback={<div className="p-6 text-sm text-muted">Loading…</div>}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Navigate to="/sources" replace />} />
|
<Route path="/" element={<Navigate to="/sources" replace />} />
|
||||||
|
|
||||||
@ -212,9 +216,11 @@ export default function App() {
|
|||||||
<Route path="/remap" element={<Remap />} />
|
<Route path="/remap" element={<Remap />} />
|
||||||
<Route path="/log" element={<Log />} />
|
<Route path="/log" element={<Log />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<BottomNav />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
40
ui/src/components/BottomNav.jsx
Normal file
40
ui/src/components/BottomNav.jsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import useTheme from '../theme.jsx'
|
||||||
|
import { NAV } from './navItems.jsx'
|
||||||
|
|
||||||
|
// Phone-sized replacement for the sidebar: same destinations, thumb-reachable.
|
||||||
|
// Hidden from md: upward, where the sidebar takes over.
|
||||||
|
export default function BottomNav() {
|
||||||
|
const { dark, setDark } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="md:hidden fixed bottom-0 inset-x-0 z-20 bg-surface border-t border-line flex justify-around items-stretch h-14 pb-[env(safe-area-inset-bottom)]">
|
||||||
|
{NAV.map(({ to, label, icon }) => (
|
||||||
|
<NavLink
|
||||||
|
key={to}
|
||||||
|
to={to}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`flex-1 flex flex-col items-center justify-center gap-0.5 text-[10px] ${
|
||||||
|
isActive ? 'text-accent' : 'text-muted'
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>{icon}</span>
|
||||||
|
{label}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => setDark(d => !d)}
|
||||||
|
className="flex-1 flex flex-col items-center justify-center gap-0.5 text-[10px] text-muted"
|
||||||
|
title={dark ? 'Light mode' : 'Dark mode'}
|
||||||
|
>
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
{dark
|
||||||
|
? <><circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="12" y1="19" x2="12" y2="22"/><line x1="2" y1="12" x2="5" y2="12"/><line x1="19" y1="12" x2="22" y2="12"/></>
|
||||||
|
: <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>}
|
||||||
|
</svg>
|
||||||
|
Theme
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,75 +1,6 @@
|
|||||||
import { NavLink } from 'react-router-dom'
|
import { NavLink } from 'react-router-dom'
|
||||||
import useTheme from '../theme.jsx'
|
import useTheme from '../theme.jsx'
|
||||||
|
import { NAV } from './navItems.jsx'
|
||||||
const NAV = [
|
|
||||||
{
|
|
||||||
to: '/sources',
|
|
||||||
label: 'Sources',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<ellipse cx="10" cy="5.5" rx="7" ry="2.5"/>
|
|
||||||
<path d="M3 5.5v9c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5v-9"/>
|
|
||||||
<path d="M3 10.5c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/import',
|
|
||||||
label: 'Import',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<line x1="10" y1="3" x2="10" y2="14"/>
|
|
||||||
<polyline points="6,10 10,14 14,10"/>
|
|
||||||
<line x1="3" y1="18" x2="17" y2="18"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/bridge',
|
|
||||||
label: 'Bridge',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<path d="M2 13a8 8 0 0 1 16 0"/>
|
|
||||||
<line x1="2" y1="13" x2="18" y2="13"/>
|
|
||||||
<line x1="7" y1="13" x2="7" y2="9"/>
|
|
||||||
<line x1="13" y1="13" x2="13" y2="9"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/remap',
|
|
||||||
label: 'Remap',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<polyline points="2,8 2,4 6,4"/>
|
|
||||||
<path d="M2 4a8 8 0 0 1 14 2"/>
|
|
||||||
<polyline points="18,12 18,16 14,16"/>
|
|
||||||
<path d="M18 16a8 8 0 0 1-14-2"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/stacks',
|
|
||||||
label: 'Stacks',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<polygon points="10,2 18,6 10,10 2,6"/>
|
|
||||||
<polyline points="2,10 10,14 18,10"/>
|
|
||||||
<polyline points="2,14 10,18 18,14"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: '/log',
|
|
||||||
label: 'Log',
|
|
||||||
icon: (
|
|
||||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
|
||||||
<circle cx="10" cy="10" r="8"/>
|
|
||||||
<polyline points="10,5 10,10 14,12"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function Sidebar({ expanded, setExpanded, loginUser, onLogout }) {
|
export default function Sidebar({ expanded, setExpanded, loginUser, onLogout }) {
|
||||||
const { dark, setDark } = useTheme()
|
const { dark, setDark } = useTheme()
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default function SourceTabs({ sources }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full min-h-0">
|
<div className="flex flex-col h-full min-h-0">
|
||||||
<div className="px-6 pt-5 shrink-0">
|
<div className="px-4 sm:px-6 pt-4 sm:pt-5 shrink-0">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link to="/sources" className="text-xs text-muted hover:text-ink-soft">Sources</Link>
|
<Link to="/sources" className="text-xs text-muted hover:text-ink-soft">Sources</Link>
|
||||||
<span className="text-muted text-xs">/</span>
|
<span className="text-muted text-xs">/</span>
|
||||||
@ -30,7 +30,7 @@ export default function SourceTabs({ sources }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex gap-1 mt-3 border-b border-line">
|
<nav className="flex gap-1 mt-3 border-b border-line overflow-x-auto whitespace-nowrap">
|
||||||
{TABS.map(({ to, label, end }) => (
|
{TABS.map(({ to, label, end }) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={label}
|
key={label}
|
||||||
|
|||||||
70
ui/src/components/navItems.jsx
Normal file
70
ui/src/components/navItems.jsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Top-level destinations, shared by the desktop sidebar and the mobile bar
|
||||||
|
export const NAV = [
|
||||||
|
{
|
||||||
|
to: '/sources',
|
||||||
|
label: 'Sources',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="10" cy="5.5" rx="7" ry="2.5"/>
|
||||||
|
<path d="M3 5.5v9c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5v-9"/>
|
||||||
|
<path d="M3 10.5c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/import',
|
||||||
|
label: 'Import',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<line x1="10" y1="3" x2="10" y2="14"/>
|
||||||
|
<polyline points="6,10 10,14 14,10"/>
|
||||||
|
<line x1="3" y1="18" x2="17" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/bridge',
|
||||||
|
label: 'Bridge',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M2 13a8 8 0 0 1 16 0"/>
|
||||||
|
<line x1="2" y1="13" x2="18" y2="13"/>
|
||||||
|
<line x1="7" y1="13" x2="7" y2="9"/>
|
||||||
|
<line x1="13" y1="13" x2="13" y2="9"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/remap',
|
||||||
|
label: 'Remap',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="2,8 2,4 6,4"/>
|
||||||
|
<path d="M2 4a8 8 0 0 1 14 2"/>
|
||||||
|
<polyline points="18,12 18,16 14,16"/>
|
||||||
|
<path d="M18 16a8 8 0 0 1-14-2"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/stacks',
|
||||||
|
label: 'Stacks',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polygon points="10,2 18,6 10,10 2,6"/>
|
||||||
|
<polyline points="2,10 10,14 18,10"/>
|
||||||
|
<polyline points="2,14 10,18 18,14"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/log',
|
||||||
|
label: 'Log',
|
||||||
|
icon: (
|
||||||
|
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="10" cy="10" r="8"/>
|
||||||
|
<polyline points="10,5 10,10 14,12"/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
@ -49,7 +49,7 @@ export default function Bridge({ sources }) {
|
|||||||
const ordered = [...banking, ...retirement]
|
const ordered = [...banking, ...retirement]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-5xl space-y-4">
|
<div className="p-4 sm:p-6 max-w-5xl space-y-4">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h1 className="text-xl font-semibold text-ink">Bridge</h1>
|
<h1 className="text-xl font-semibold text-ink">Bridge</h1>
|
||||||
<button
|
<button
|
||||||
@ -82,7 +82,7 @@ export default function Bridge({ sources }) {
|
|||||||
title="SimpleFIN accounts"
|
title="SimpleFIN accounts"
|
||||||
description="Every account behind the bridge credential, and which source pulls from it."
|
description="Every account behind the bridge credential, and which source pulls from it."
|
||||||
>
|
>
|
||||||
<table className="w-full text-xs">
|
<table className="w-full text-xs hidden sm:table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="text-left text-muted border-b border-line-soft">
|
<tr className="text-left text-muted border-b border-line-soft">
|
||||||
<th className="pb-1 font-medium">Account</th>
|
<th className="pb-1 font-medium">Account</th>
|
||||||
@ -132,6 +132,42 @@ export default function Bridge({ sources }) {
|
|||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
{/* Stacked cards for narrow screens */}
|
||||||
|
<div className="sm:hidden divide-y divide-line-soft">
|
||||||
|
{ordered.map(a => {
|
||||||
|
const src = sourceFor(a.id)
|
||||||
|
return (
|
||||||
|
<div key={a.id} className="py-2">
|
||||||
|
<div className="flex justify-between gap-2">
|
||||||
|
<span className="text-xs text-ink-soft">{a.name}</span>
|
||||||
|
<span className="text-xs font-mono text-ink">{money(a.balance)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between gap-2 text-xs text-muted">
|
||||||
|
<span>{a.organization}</span>
|
||||||
|
<span>{a.balance_date}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs mt-0.5">
|
||||||
|
{src
|
||||||
|
? <Link to={`/sources/${encodeURIComponent(src.name)}`} className="text-accent">{src.name}</Link>
|
||||||
|
: <span className="text-muted">not linked</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<div className="pt-2 flex justify-between text-xs">
|
||||||
|
<span className="text-muted">Banking and cards</span>
|
||||||
|
<span className="font-mono text-ink-soft">{money(sum(banking))}</span>
|
||||||
|
</div>
|
||||||
|
<div className="pt-1 flex justify-between text-xs border-0">
|
||||||
|
<span className="text-muted">Retirement</span>
|
||||||
|
<span className="font-mono text-ink-soft">{money(sum(retirement))}</span>
|
||||||
|
</div>
|
||||||
|
<div className="pt-2 flex justify-between text-xs font-medium">
|
||||||
|
<span className="text-ink-soft">Total</span>
|
||||||
|
<span className="font-mono text-ink">{money(sum(accounts))}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{accounts.length === 0 && <p className="text-xs text-muted">No accounts returned.</p>}
|
{accounts.length === 0 && <p className="text-xs text-muted">No accounts returned.</p>}
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -168,10 +168,10 @@ export default function Import({ source }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source) return <div className="p-6 text-sm text-muted">Select a source first.</div>
|
if (!source) return <div className="p-4 sm:p-6 text-sm text-muted">Select a source first.</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-2xl">
|
<div className="p-4 sm:p-6 max-w-2xl">
|
||||||
<h1 className="text-xl font-semibold text-ink mb-6">Import — {source}</h1>
|
<h1 className="text-xl font-semibold text-ink mb-6">Import — {source}</h1>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ export default function ImportHub({ sources }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-4xl space-y-6">
|
<div className="p-4 sm:p-6 max-w-4xl space-y-6">
|
||||||
<h1 className="text-xl font-semibold text-ink">Import</h1>
|
<h1 className="text-xl font-semibold text-ink">Import</h1>
|
||||||
|
|
||||||
{feeds.length > 0 && (
|
{feeds.length > 0 && (
|
||||||
|
|||||||
@ -354,7 +354,7 @@ export default function Mappings({ source, onNeedsReprocess }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source) return <div className="p-6 text-sm text-muted">Select a source first.</div>
|
if (!source) return <div className="p-4 sm:p-6 text-sm text-muted">Select a source first.</div>
|
||||||
|
|
||||||
const displayRows = sortedRows(filteredRows)
|
const displayRows = sortedRows(filteredRows)
|
||||||
|
|
||||||
|
|||||||
@ -370,7 +370,7 @@ export default function Pivot({ source, selectedStack, setSelectedStack }) {
|
|||||||
viewer.restore({ table: selectedView, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG })
|
viewer.restore({ table: selectedView, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source) return <div className="p-6 text-sm text-muted">Select a source first.</div>
|
if (!source) return <div className="p-4 sm:p-6 text-sm text-muted">Select a source first.</div>
|
||||||
|
|
||||||
const cols = inspectedRows?.length ? Object.keys(inspectedRows[0]) : []
|
const cols = inspectedRows?.length ? Object.keys(inspectedRows[0]) : []
|
||||||
|
|
||||||
|
|||||||
@ -283,7 +283,7 @@ export default function Records({ source }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source) return <div className="p-6 text-sm text-muted">Select a source first.</div>
|
if (!source) return <div className="p-4 sm:p-6 text-sm text-muted">Select a source first.</div>
|
||||||
|
|
||||||
const displayCols = gridCols(rows.length > 0 ? Object.keys(rows[0]) : cols)
|
const displayCols = gridCols(rows.length > 0 ? Object.keys(rows[0]) : cols)
|
||||||
const visCols = gridCols(cols)
|
const visCols = gridCols(cols)
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export default function Remap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-4xl">
|
<div className="p-4 sm:p-6 max-w-4xl">
|
||||||
<h1 className="text-base font-semibold text-ink mb-4">Remap Output Values</h1>
|
<h1 className="text-base font-semibold text-ink mb-4">Remap Output Values</h1>
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
|
|||||||
@ -310,10 +310,10 @@ export default function Rules({ source, onStale }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!source) return <div className="p-6 text-sm text-muted">Select a source first.</div>
|
if (!source) return <div className="p-4 sm:p-6 text-sm text-muted">Select a source first.</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-3xl">
|
<div className="p-4 sm:p-6 max-w-3xl">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="text-xl font-semibold text-ink">Rules — {source}</h1>
|
<h1 className="text-xl font-semibold text-ink">Rules — {source}</h1>
|
||||||
<button onClick={startCreate}
|
<button onClick={startCreate}
|
||||||
|
|||||||
@ -156,10 +156,10 @@ export default function SourceDetail({ sources, setSources }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sourceObj) return <div className="p-6 text-sm text-muted">Source not found.</div>
|
if (!sourceObj) return <div className="p-4 sm:p-6 text-sm text-muted">Source not found.</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-5xl space-y-4">
|
<div className="p-4 sm:p-6 max-w-5xl space-y-4">
|
||||||
{stats && (
|
{stats && (
|
||||||
<div className="flex gap-4 text-xs">
|
<div className="flex gap-4 text-xs">
|
||||||
<span className="text-muted"><span className="font-medium text-ink">{stats.total_records}</span> total</span>
|
<span className="text-muted"><span className="font-medium text-ink">{stats.total_records}</span> total</span>
|
||||||
|
|||||||
@ -124,7 +124,7 @@ export default function SourceList({ sources, setSources, setSource }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 max-w-5xl">
|
<div className="p-4 sm:p-6 max-w-5xl">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="text-xl font-semibold text-ink">Sources</h1>
|
<h1 className="text-xl font-semibold text-ink">Sources</h1>
|
||||||
{!creating && (
|
{!creating && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user