/* DigiHealthCloud — device catalog page. * * Reads data/devices.json (built weekly from openFDA by scrapers/devices.mjs) * and renders it as a sortable, filterable table. No backend: the whole * catalog is one JSON file, filtered and sorted in the browser. */ const { useState, useEffect, useMemo } = React; const Header = () => (
DigiHealthCloud
); const Empty = ({ building }) => (

{building ? 'The catalog is still being built' : 'Nothing matches those filters'}

{building ? 'The first scrape runs on the weekly schedule, or can be started by hand from the repository. Once it has run, every cleared device appears here.' : 'Try clearing the search box or widening the modality and year filters.'}

); /* Sortable column header. Clicking toggles direction; the arrow shows state. */ const Th = ({ label, column, sort, setSort, className }) => { const active = sort.key === column; const toggle = () => setSort({ key: column, dir: active && sort.dir === 'asc' ? 'desc' : 'asc' }); return ( ); }; const App = () => { const [data, setData] = useState(null); const [failed, setFailed] = useState(false); const [q, setQ] = useState(''); const [modality, setModality] = useState(''); const [company, setCompany] = useState(''); const [year, setYear] = useState(''); const [sort, setSort] = useState({ key: 'decisionDate', dir: 'desc' }); useEffect(() => { fetch('data/devices.json', { cache: 'no-cache' }) .then((r) => (r.ok ? r.json() : Promise.reject(new Error('missing')))) .then(setData) .catch(() => setFailed(true)); }, []); const devices = data?.devices || []; const rows = useMemo(() => { const needle = q.trim().toLowerCase(); const out = devices.filter((d) => { if (modality && !d.modalities.includes(modality)) return false; if (company && d.company !== company) return false; if (year && d.year !== year) return false; if (needle) { const hay = `${d.name} ${d.company} ${d.kNumber} ${d.classification || ''}`.toLowerCase(); if (!hay.includes(needle)) return false; } return true; }); const dir = sort.dir === 'asc' ? 1 : -1; return out.sort((a, b) => { const av = (a[sort.key] ?? '').toString().toLowerCase(); const bv = (b[sort.key] ?? '').toString().toLowerCase(); if (av === bv) return a.name.localeCompare(b.name); return av.localeCompare(bv) * dir; }); }, [devices, q, modality, company, year, sort]); const activeFilters = Boolean(q || modality || company || year); const clear = () => { setQ(''); setModality(''); setCompany(''); setYear(''); }; const shown = rows.slice(0, 400); return ( <>
Reference

Digital health device catalog

US device clearances for digital measurement — wearables, sensors, algorithms and software — tagged by modality so you can see what has already reached the market in the area you're measuring.

{failed || (data && !devices.length) ? ( ) : !data ? (

Loading the catalog…

) : ( <>
setQ(e.target.value)} />
{rows.length.toLocaleString()} {rows.length === devices.length ? ' devices' : ` of ${devices.length.toLocaleString()} devices`} {activeFilters && ( )}
{rows.length === 0 ? : (
{shown.map((d) => ( ))}
Modality FDA record
{d.name} {d.classification && {d.classification}} {d.company} {d.modalities.map((m) => {m})} {d.decisionDate || '—'} {d.deviceClass || '—'} {d.kNumber}
)} {rows.length > shown.length && (

Showing {shown.length} of {rows.length.toLocaleString()} matches — narrow the filters to see the rest.

)}

{data.count.toLocaleString()} clearances · {data.coverage} Source:{' '} openFDA 510(k) , US government data in the public domain. Modality tags are keyword-derived from the FDA device and classification names — follow any device through to its FDA record for the authoritative detail. Last updated{' '} {data.updatedAt ? new Date(data.updatedAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : '—'}.

)}
); }; ReactDOM.createRoot(document.getElementById('root')).render();