import { useRef, useState } from 'react'; import { AlertCircle, FileUp, Loader2 } from 'lucide-react'; import { Button } from '../../../components/ui/button.tsx'; import { Alert, AlertDescription } from '../../../components/ui/alert.tsx'; import { extractPdfText, NoTextLayerError } from '../../../utils/pdfText.ts'; // FILLING AN AGREEMENT EDITOR FROM A PDF, so a practice does not retype its own documents. // // Spencer: "it would be faster and easier for them to do that than to try and manually type it out // or copy/paste out of the pdf themselves." The reason for the PDF was never the PDF. It is an INPUT // to the box, and what gets stored, versioned and pinned to a Consent is our own text. // // IT FILLS THE BOX AND NEVER SAVES, and that is not a UI preference. Extraction loses layout: headers // land in the body, columns read across instead of down, and text in sidebars or tables can arrive out // of order or NOT AT ALL. A clause that silently fails to extract produces an agreement missing a // clause, which patients then agree to - and nothing downstream can catch it, because the version, the // signature and the audit trail are all valid records of the wrong document. A human reading the box is // the only check there is. export const FillFromPdf = ({ instrumentName, disabled, onExtracted, }: { /** Named so the control says which of the three documents it is about to overwrite. */ instrumentName: string; disabled: boolean; onExtracted: (text: string) => void; }) => { const inputRef = useRef(null); const [working, setWorking] = useState(false); const [error, setError] = useState(); const [filled, setFilled] = useState<{ pageCount: number }>(); const handleFile = async (file: File | undefined) => { if (!file) return; setWorking(true); setError(undefined); setFilled(undefined); try { const { text, pageCount } = await extractPdfText(file); onExtracted(text); setFilled({ pageCount }); } catch (failure) { // A SCAN IS ITS OWN SENTENCE, because it is the one failure a practice can act on and the // advice is specific. A generic "could not read the file" sends somebody to check the file // is not corrupt, when what they need is to find a text copy. setError(failure instanceof NoTextLayerError ? failure.message : 'We could not read that PDF. Check it opens normally, or paste the text in instead.'); } finally { setWorking(false); // Cleared so choosing the SAME file again re-runs. Without this a practice who fixes the // file and picks it again gets nothing, and the control looks broken. if (inputRef.current) inputRef.current.value = ''; } }; return (
void handleFile(event.target.files?.[0])} /> {filled && ( Filled from {filled.pageCount} page{filled.pageCount === 1 ? '' : 's'}. Check it below, then save. )}
{/* * THE DISCLAIMER SITS INSIDE THE CONTROL, not beneath the editor, so it is read while the * result can still be checked. Three things it has to say, and the middle one is the one * nobody guesses. */}

We copy the text out of the PDF into the box below — patients never see the file itself, only what ends up in the box. Formatting can be lost, and text in sidebars, columns or tables can arrive out of order or not at all. It is a convenience: please read what lands there and correct it before saving.

{error && ( {error} )}
); }; export default FillFromPdf;