import { Fragment, useState } from "react"; import { Button } from "../../../components/ui/button.tsx"; import { Checkbox } from "../../../components/ui/checkbox.tsx"; import { Input } from "../../../components/ui/input.tsx"; import { Label } from "../../../components/ui/label.tsx"; import { ToggleGroupControl, ToggleGroupControlOption } from "../../../components/ui/toggle-group-control.tsx"; import type { CandidateDecision, ImportCandidate, ImportCandidateFields, UncertainMark } from "../../../services/RecordsService.tsx"; import { absentNote, FIELD_LABEL, KIND_LABEL, leaveOutLabel, limitLabel, MARK_LABEL, quoted, restrictionNote, SEPARATOR, structuredSource, unmatchedNote } from "./recordsImportWords.ts"; interface Props { candidate: ImportCandidate; /** Why adding to the chart is not possible yet, shown instead of the Add and Try adding again buttons. */ blocked?: string; mayDecide: boolean; busy: boolean; onDecide: (decision: Pick) => void; } const EDITABLE = ["text", "date", "dosage", "value", "reaction"] as const; const ImportCandidateRow = ({ candidate, blocked, mayDecide, busy, onDecide }: Props) => { const [editing, setEditing] = useState(false); const [edits, setEdits] = useState>({}); const [confirmed, setConfirmed] = useState([]); // Checks and corrections belong to the proposal they were made against; a changed one starts unchecked. const identity = [candidate.candidateId, candidate.version, candidate.digest ?? JSON.stringify([candidate.fields, candidate.uncertain, candidate.source])].join("|"); const [checkedAgainst, setCheckedAgainst] = useState(identity); if (checkedAgainst !== identity) { setCheckedAgainst(identity); setEditing(false); setEdits({}); setConfirmed([]); } const fields = { ...candidate.fields, ...edits }; const edited = new Set(Object.keys(edits)); const open = candidate.uncertain.filter((mark) => !edited.has(mark) && !confirmed.includes(mark)); const checks = open.filter((mark) => mark !== "assertion"); // A value the reader could not tie to this item is not offered; staff enter it, or leave it out of the entry. const unassigned = (mark: UncertainMark) => (EDITABLE as readonly string[]).includes(mark) && fields[mark as typeof EDITABLE[number]] === undefined; const id = "import-" + candidate.candidateId; const facts = [KIND_LABEL[candidate.kind], fields.date, fields.dosage, fields.value, fields.reaction].filter(Boolean).join(SEPARATOR); const commit = candidate.commit; // What the chart already holds from an earlier import of this source item: the same version unchanged is declined, and // a copy changed here is looked at, at the version it is now, before this one is added beside it. const prior = candidate.alreadyImported ?? []; const sameCopy = prior.find((found) => found.sameVersion && !found.editedHere); const changedHere = prior.filter((found) => found.editedHere); const unreadable = changedHere.filter((found) => !found.localVersion); const seenKey = JSON.stringify(changedHere); const [seenFor, setSeenFor] = useState(); const seen = changedHere.length > 0 && seenFor === seenKey; const acknowledged = seen ? changedHere.map((found) => ({ resource: found.resource, version: found.localVersion! })) : undefined; const addable = !sameCopy && unreadable.length === 0 && (changedHere.length === 0 || seen); const source = candidate.structured ? structuredSource(candidate.source.item, candidate.source.version) : undefined; const pending = candidate.decision === "pending" && mayDecide; const resumable = candidate.decision === "accepted" && (commit?.state === "committing" || commit?.state === "evidence-owed"); // A refused item the server lets be tried again is held to the same chart checks as adding it. const retrying = commit?.state === "rejected" && Boolean(candidate.retryable) && mayDecide; const restricted = restrictionNote(candidate.restrictions); return (
  • {fields.text}{fields.assertion === "absent" ? absentNote(candidate.kind) : ""} {facts} {candidate.source.lines?.length ? ( {candidate.source.lines.map((line) => "Page " + line.page + ", line " + line.line + ": " + quoted(line.text)).join(" ")} ) : candidate.structured ? {source ?? "From the structured record that was sent"} : null} {restricted && {restricted}} {candidate.limits?.map((limit) => ( {limitLabel(limit)} ))} {candidate.possibleDuplicates?.length ? May already be on this chart : null} {sameCopy && ( Already in this chart from an earlier import of this same version ({sameCopy.resource}). Decline this copy. )} {prior.some((found) => found.missingRestrictions === true) && ( An existing chart copy does not carry all the source restrictions. That copy is unchanged. )} {prior.filter((found) => !found.sameVersion && !found.editedHere).map((found) => ( An earlier version of this item is already in this chart from an earlier import ({found.resource}). ))} {changedHere.map((found) => ( {found.localVersion ? "An earlier import of this item was changed in this chart since: " + found.resource + ", now version " + found.localVersion + "." : "An earlier import of this item was changed in this chart since, and " + found.resource + " could not be read as it is now. Try again later."} ))} {candidate.priorCheck === "unknown" && ( Whether this item is already in this chart could not be checked; adding it checks again. )} {candidate.decision === "declined" && Declined} {candidate.decision === "accepted" && commit?.state === "committed" && ( Added to the chart{commit.refusals?.length ? " after the record service refused an earlier attempt" : ""} )} {commit?.state === "rejected" && ( Not added. {commit.reason} {retrying && !blocked && !sameCopy && unreadable.length === 0 && ( )} )} {resumable && ( {commit?.state === "evidence-owed" ? "Added; still recording who added it" : "Not confirmed yet"} {mayDecide && } )} {pending && ( {!candidate.structured && !editing && } {!blocked && !sameCopy && unreadable.length === 0 && ( )} )}
    {(pending || retrying) && blocked &&

    {blocked}

    } {pending && editing && (
    {EDITABLE.filter((name) => name === "text" || candidate.fields[name] !== undefined || name in edits || candidate.uncertain.includes(name)).map((name) => (
    setEdits({ ...edits, [name]: event.target.value })} />
    ))} setEdits({ ...edits, assertion: value === "absent" ? "absent" : "present" })}>
    )} {pending && checks.length > 0 && (
    {checks.map((mark) => ( {unassigned(mark) &&

    {unmatchedNote(mark)}

    }
    ))}
    )} {(pending || retrying) && changedHere.length > 0 && unreadable.length === 0 && (
    )} {pending && fields.assertion === "uncertain" && !editing && (

    Say whether the original records this as present or not: choose Correct.

    )}
  • ); }; export default ImportCandidateRow;