inapa-data-review-ui
v0.2.0
Published
Reusable 3-panel data digitization/validation UI — file/related-item list + document viewer (image/PDF/text) + schema-driven form, for human-in-the-loop data entry.
Maintainers
Readme
inapa-data-review-ui
Reusable 3-panel data digitization/validation UI — a related-items list, a document viewer (image / PDF / plain text), and a schema-driven field form, laid out in resizable panels. Built for human-in-the-loop data entry (reviewing OCR'd invoices, validating imported records, etc.).
100% presentational: no network calls, no domain knowledge (no business rules, no field lists hardcoded to a particular use case). All of that lives in the consuming app, which wires its own data through props.
Install
Published on npm:
npm install inapa-data-review-uiFor local development against a checkout, npm link it into the consuming
app instead.
Usage
Import the stylesheet once (it carries react-pdf's text/annotation layer
CSS — skip it and PDF text selection/rendering will look broken):
import 'inapa-data-review-ui/style.css';import { DataReviewPanel } from 'inapa-data-review-ui';
<DataReviewPanel
title="Factura OCR — NIC 12345"
chips={[{ label: 'EDESUR' }, { label: '05/2026' }]}
actions={[
{ label: 'Validar', variant: 'contained', onClick: handleValidate },
{ label: 'Invalidar', color: 'error', onClick: handleInvalidate },
]}
relatedItemsTitle="Registros NIC 12345"
relatedItems={siblingRecords.map(toRelatedItem)}
document={{
kind: 'image',
url: imageUrl,
overlayTokens: detectedWords, // pre-matched by the caller (green=matched, yellow=unmatched)
}}
onPickText={(text) => {/* optional extra side effect; the focused field is auto-filled */}}
sections={[
{
title: 'IDENTIFICACIÓN',
fields: [
{ key: 'nic', label: 'NIC', type: 'text', gridWidth: 6, required: true },
{ key: 'numero_medidor', label: 'Medidor', type: 'text', gridWidth: 6 },
],
},
{
title: 'MONTOS (RD$)',
fields: [
{ key: 'total_facturado', label: 'Total', type: 'currency', gridWidth: 6, format: fmtRD, parse: parseRD },
],
},
]}
values={editableFields}
onChange={(key, value) => setEditableFields((f) => ({ ...f, [key]: value }))}
validationMessages={qcWarnings} // key -> string[] business-rule warnings, computed by the caller
autoSaveId="ocr-review" // persist panel widths across sessions (localStorage)
/>PromptVersionManager
For versioning "what prompt is used + what output is expected" as a single unit — the same shape whether the storage is SQLite-with-activate or ClickHouse-append-only. Also 100% presentational.
import { PromptVersionManager, sectionsToJsonSchema } from 'inapa-data-review-ui';
<PromptVersionManager
versions={promptVersions} // PromptVersion[] — each bundles prompt + inputDescription + outputSections
activeVersionId={activeId}
onActivate={(id) => activatePromptVersion(id)} // omit if your versioning is append-only (no activate step)
onSave={(draft) => savePromptVersion(draft)} // always a NEW version — nothing is edited in place
renderVersionMeta={(v) => <span>{v.meta?.usageCount} usos</span>} // optional slot for cost/usage stats you already track
/>outputSections uses ExtractionSectionDef[] — the same shape as
DataReviewPanel's sections, minus format/parse (functions aren't
serializable to a DB row or a JSON textarea). The point: the same field
list that defines the review form can also generate the JSON Schema you
send to an LLM for extraction, via:
const { name, strict, schema } = sectionsToJsonSchema(version.outputSections);
// -> OpenAI Structured Outputs-compatible schema, ready to pass as response_formatOne source of truth for "what fields does this extract" instead of hand- maintaining a form config and an LLM schema separately.
What this package does NOT do
- No fetch/save/validate network calls — wire your own via
actions. - No business validation rules — compute
validationMessagesyourself and pass them in. - No OCR — if using the image viewer's overlay, detect/match text yourself and pass
overlayTokens. - No currency/locale formatting defaults — pass
format/parseper field.
Local development
npm install
npm run dev # opens the dev/ playground app with mock data
npm run build # builds the library to dist/