Lets staff attach files to an appointment directly from the edit form via local disk upload, picking a previously-saved scanned attachment, or an inline screenshot with crop-before-save (matching the Copy Agent page's crop UX), instead of detouring through the Copy Agent page. Also fixes a latent data-loss bug in claim-form's save-for-appointment call, which replaced all AppointmentFile rows instead of merging with existing ones. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { useRef, useState } from "react";
|
|
|
|
// Minimal single-shot screen capture, extracted from the batching/cropping logic in
|
|
// ai-copy-agent-page.tsx — this just needs "take a screenshot, attach it."
|
|
export function useScreenCapture() {
|
|
const [isCapturing, setIsCapturing] = useState(false);
|
|
const [countdown, setCountdown] = useState<number | null>(null);
|
|
const streamRef = useRef<MediaStream | null>(null);
|
|
|
|
const stopStream = () => {
|
|
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
streamRef.current = null;
|
|
};
|
|
|
|
// Resolves with a PNG data URL of the full screenshot (caller crops before converting to a File).
|
|
async function capture(): Promise<string> {
|
|
setIsCapturing(true);
|
|
try {
|
|
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
video: { displaySurface: "monitor" },
|
|
});
|
|
streamRef.current = stream;
|
|
|
|
const video = document.createElement("video");
|
|
video.srcObject = stream;
|
|
video.muted = true;
|
|
await video.play();
|
|
await new Promise<void>((resolve) => {
|
|
if (video.readyState >= 2) resolve();
|
|
else video.onloadeddata = () => resolve();
|
|
});
|
|
|
|
// Short countdown so the user has time to alt-tab to the window they want to capture.
|
|
for (let secondsLeft = 2; secondsLeft > 0; secondsLeft--) {
|
|
setCountdown(secondsLeft);
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
}
|
|
setCountdown(null);
|
|
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
canvas.getContext("2d")?.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
|
|
stopStream();
|
|
|
|
return canvas.toDataURL("image/png");
|
|
} catch (error) {
|
|
stopStream();
|
|
const name = error instanceof Error ? error.name : "";
|
|
const message =
|
|
name === "NotAllowedError"
|
|
? "Screen share permission was denied or the picker was cancelled."
|
|
: name === "NotFoundError"
|
|
? "No screen/window source was available to capture."
|
|
: !navigator.mediaDevices?.getDisplayMedia
|
|
? "Screen capture isn't supported in this browser."
|
|
: error instanceof Error
|
|
? error.message
|
|
: "Screen capture failed.";
|
|
throw new Error(message);
|
|
} finally {
|
|
setIsCapturing(false);
|
|
setCountdown(null);
|
|
}
|
|
}
|
|
|
|
return { capture, isCapturing, countdown };
|
|
}
|