feat: save claim attachments to cloud storage and documents page

- Claim file uploads (chatbot or manual) now save to both the Cloud
  Storage patient folder and the Documents page via new
  POST /api/claims/upload-to-cloud endpoint
- MH submit flow now calls uploadAttachmentsToLocalFolder (same as
  DDMA/United/Tufts) so chatbot-attached X-rays are persisted
- Removed old /upload-attachments disk route and attachmentDiskStorage
  multer config; deleted uploads/patients/ folder
- uploadAttachmentsToLocalFolder now points to /upload-to-cloud and
  sends patientId so the backend can create the patient folder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-11 00:30:32 -04:00
parent 46daeb1c1f
commit d4b9c1b889
10 changed files with 280 additions and 68 deletions

View File

@@ -59,9 +59,14 @@ Intents:
e.g. "check Maria Jesus", "verify insurance for John Smith"
- eligibility_by_id : user provides a member ID and date of birth (no patient name)
e.g. "check masshealth for 100xxxx, 10/10/1988"
- check_and_claim : user wants to check eligibility AND submit procedures as claims
ALSO use this when user wants to check eligibility AND schedule/add an appointment on a date
e.g. "check mh for 100xxxx, 10/10/1988 and schedule on 4/10/2026"
e.g. "check mh for 100xxxx, 10/10/1988 and make appointment on 5/1/2026"
In these cases set appointmentDate to the mentioned date (YYYY-MM-DD)
- check_and_claim : user wants to check eligibility AND submit PROCEDURES/BILLING as claims
e.g. "check masshealth for 100xxxx, 10/10/1988 and claim perio exam and adult cleaning"
e.g. "check Maria Jesus and claim D0120 D1110"
Only use this when procedures or CDT codes are mentioned — NOT for scheduling
- find_patient : look up a patient record only, no eligibility
e.g. "find patient John", "look up Smith"
- schedule_appointment : add a patient to the schedule (today or a specified date/time)
@@ -84,6 +89,11 @@ Intents:
Rules:
- For check_and_claim and claim_only, procedureNames should be the RAW user text
(e.g. "perio exam", "adult cleaning", "D0120") — do NOT translate to codes
- IMPORTANT: If the user says "with the x ray", "with x ray", "attach x ray", "the x ray", "with xray",
"with the attachment", "the attachment", "with attachment", "with the file", "with the image",
"with the scan", "with the photo", "with the document", or any variation meaning an uploaded/attached file,
do NOT include it in procedureNames. It refers to a file attachment, not a billable procedure.
Only include actual clinical procedures in procedureNames.
- For composite fillings with a tooth number, preserve the EXACT notation including tooth# and surfaces:
e.g. "composite #29 O", "#8 MO", "composite #11 MOD" — keep the #number and surface letters together as one entry
- For RCT/root canal with a tooth number, preserve the tooth# in the entry:

View File

@@ -11,6 +11,13 @@ import { ChatClassification } from "./internal-chat-graph";
import { lookupCdtCodes } from "./cdt-lookup";
import insuranceAliases from "../data/insuranceAliases.json";
// Phrases the user may write to mean "attach the uploaded file" — not CDT procedures
const ATTACHMENT_PHRASES = /^(with\s+)?(the\s+)?(x[\s-]?ray[s]?|xray[s]?|radiograph[s]?|image[s]?|film[s]?|attachment[s]?|attach|file[s]?|photo[s]?|picture[s]?|scan[s]?|doc(ument)?[s]?)$/i;
function stripAttachmentRefs(names: string[]): string[] {
return names.filter((n) => !ATTACHMENT_PHRASES.test(n.trim()));
}
// ─── Types ────────────────────────────────────────────────────────────────────
export interface ResolvedPatient {
@@ -358,6 +365,7 @@ async function handleEligibilityById(
dob: resolvedDob,
siteKey,
autoCheck: siteKeyToAutoCheck(siteKey),
appointmentDate: c.appointmentDate ?? null,
},
};
}
@@ -423,7 +431,7 @@ async function handleCheckAndClaim(
}
// 3. Map procedure names → CDT codes (custom aliases take priority)
const procedureNames = c.procedureNames ?? [];
const procedureNames = stripAttachmentRefs(c.procedureNames ?? []);
const cdtResults: CdtResult[] = procedureNames.length > 0
? lookupCdtCodes(procedureNames, customAliases)
: [];
@@ -492,7 +500,7 @@ async function handleClaimOnly(
const fullName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
// Map procedure names → CDT codes
const procedureNames = c.procedureNames ?? [];
const procedureNames = stripAttachmentRefs(c.procedureNames ?? []);
if (procedureNames.length === 0) {
return { reply: "Please specify which procedures to claim (e.g. perio exam, adult prophy)." };
}
@@ -741,15 +749,16 @@ async function handleScheduleAppointment(
export async function createAppointmentToday(
patientId: number,
userId: number,
storage: StorageLike
storage: StorageLike,
targetDate?: string // YYYY-MM-DD; defaults to today
): Promise<{ startTime: string; endTime: string; dateStr: string; dateLabel: string; column: string } | { error: string }> {
const today = new Date();
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}-${String(today.getDate()).padStart(2, "0")}`;
const dateLabel = today.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
const localDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const base = targetDate ? new Date(targetDate + "T00:00:00") : new Date();
const dateStr = `${base.getFullYear()}-${String(base.getMonth() + 1).padStart(2, "0")}-${String(base.getDate()).padStart(2, "0")}`;
const dateLabel = base.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
const localDate = new Date(base.getFullYear(), base.getMonth(), base.getDate());
const dayNames = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
const dayName = dayNames[today.getDay()]!;
const dayName = dayNames[base.getDay()]!;
const officeHours = await storage.getOfficeHours(userId);
const dayHours = officeHours?.data?.doctors?.[dayName] ?? {
@@ -757,7 +766,7 @@ export async function createAppointmentToday(
};
if (!dayHours.enabled) {
return { error: `The office is closed today (${dayName}). Cannot create appointment.` };
return { error: `The office is closed on ${dayName} (${dateLabel}). Cannot create appointment.` };
}
const allSlots = buildSlots(dayHours);

View File

@@ -78,7 +78,7 @@ export const startBackupCron = () => {
const log = await cronJobLogStorage.createJobLog("local-backup", startedAt);
try {
const filename = `dental_backup_${Date.now()}.sql`;
const filename = `dental_backup_${Date.now()}.zip`;
await backupDatabaseToPath({ destinationPath: LOCAL_BACKUP_DIR, filename });
pruneOldBackups(LOCAL_BACKUP_DIR);
await storage.createBackup(admin.id);
@@ -153,7 +153,7 @@ export const startBackupCron = () => {
}
try {
const filename = `dental_backup_usb_${Date.now()}.sql`;
const filename = `dental_backup_usb_${Date.now()}.zip`;
await backupDatabaseToPath({ destinationPath: usbBackupPath, filename });
pruneOldBackups(usbBackupPath);
await storage.createBackup(admin.id);

View File

@@ -313,9 +313,9 @@ router.post("/create-appointment-today", async (req: Request, res: Response): Pr
try {
const userId = req.user?.id;
if (!userId) return res.status(401).json({ message: "Unauthorized" });
const { patientId } = req.body;
const { patientId, date } = req.body;
if (!patientId) return res.status(400).json({ message: "patientId is required" });
const result = await createAppointmentToday(Number(patientId), userId, storage);
const result = await createAppointmentToday(Number(patientId), userId, storage, date ?? undefined);
if ("error" in result) return res.status(409).json({ message: result.error });
return res.status(200).json(result);
} catch (err) {

View File

@@ -90,45 +90,66 @@ const upload = multer({
},
});
// Disk-storage uploader for claim attachments saved under uploads/patients/<name>/
const attachmentDiskStorage = multer.diskStorage({
destination: (req, _file, cb) => {
const patientName = String(req.body.patientName || "unknown").replace(/[/\\?%*:|"<>]/g, "-").trim();
const dir = path.join(process.cwd(), "uploads", "patients", patientName);
fs.mkdirSync(dir, { recursive: true });
cb(null, dir);
},
filename: (_req, file, cb) => {
const ext = path.extname(file.originalname);
const base = path.basename(file.originalname, ext).replace(/\s+/g, "_");
cb(null, `${Date.now()}_${base}${ext}`);
},
});
const attachmentUpload = multer({
storage: attachmentDiskStorage,
limits: { fileSize: 20 * 1024 * 1024 },
fileFilter: (_req, file, cb) => {
const allowed = ["application/pdf", "image/jpeg", "image/png", "image/webp"];
cb(null, allowed.includes(file.mimetype));
},
});
// POST /api/claims/upload-attachments
// Saves files to uploads/patients/<patientName>/ and returns their paths.
// POST /api/claims/upload-to-cloud
// Saves uploaded files to both:
// - Patient Documents (visible on Documents page)
// - Cloud Storage patient folder (visible on Cloud Storage page)
router.post(
"/upload-attachments",
attachmentUpload.array("files", 10),
(req: Request, res: Response): any => {
"/upload-to-cloud",
upload.array("files", 10),
async (req: Request, res: Response): Promise<any> => {
if (!req.user?.id) return res.status(401).json({ error: "Unauthorized" });
const files = req.files as Express.Multer.File[];
if (!files?.length) return res.status(400).json({ error: true, message: "No files uploaded" });
if (!files?.length) return res.status(400).json({ error: "No files uploaded" });
const result = files.map((f) => ({
filename: f.originalname,
mimeType: f.mimetype,
filePath: `/uploads/patients/${path.basename(path.dirname(f.path))}/${path.basename(f.path)}`,
}));
const patientId = Number(req.body.patientId);
const patientName = String(req.body.patientName || "unknown").replace(/[/\\?%*:|"<>]/g, "-").trim();
return res.json({ error: false, data: result });
if (!patientId || isNaN(patientId)) {
return res.status(400).json({ error: "Invalid patientId" });
}
try {
const folder = await storage.getOrCreatePatientFolder(req.user.id, patientId, patientName);
const result: { filename: string; mimeType: string; filePath: string }[] = [];
for (const file of files) {
// Save to patient-documents (Documents page)
await storage.createPatientDocument(
patientId,
file.originalname,
file.originalname,
file.mimetype,
file.size,
file.buffer
);
// Save to cloud storage patient folder (Cloud Storage page)
const cloudFile = await storage.initializeFileUpload(
req.user.id,
file.originalname,
file.mimetype,
BigInt(file.size),
1,
(folder as any).id
);
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
await storage.finalizeFileUpload((cloudFile as any).id);
result.push({
filename: file.originalname,
mimeType: file.mimetype,
filePath: `/api/cloud-storage/files/${(cloudFile as any).id}/content`,
});
}
return res.json({ error: false, data: result });
} catch (err: any) {
console.error("[upload-to-cloud]", err);
return res.status(500).json({ error: "Failed to upload files", message: err?.message });
}
}
);

View File

@@ -370,7 +370,7 @@ router.post("/backup-path", async (req, res) => {
});
}
const filename = `dental_backup_${Date.now()}.sql`;
const filename = `dental_backup_${Date.now()}.zip`;
try {
await backupDatabaseToPath({

View File

@@ -899,11 +899,10 @@ export function ClaimForm({
...formToCreateClaim
} = f;
// build claimFiles metadata from uploadedFiles (only filename + mimeType)
const claimFilesMeta: ClaimFileMeta[] = (uploadedFiles || []).map((f) => ({
filename: f.name,
mimeType: f.type,
}));
// Save uploaded files to disk (uploads/patients/<name>/) and record their paths
const claimFilesMeta: ClaimFileMeta[] = uploadedFiles?.length
? await uploadAttachmentsToLocalFolder(uploadedFiles)
: [];
const selectedNpiProviderId = npiProvider?.npiNumber
? npiProviders.find((p) => p.npiNumber === npiProvider.npiNumber)?.id ?? null
@@ -1423,10 +1422,11 @@ export function ClaimForm({
: patient?.firstName ?? `patient-${patientId}`;
const formData = new FormData();
formData.append("patientId", String(patientId));
formData.append("patientName", patientName);
files.forEach((f) => formData.append("files", f));
const res = await apiRequest("POST", "/api/claims/upload-attachments", formData);
const res = await apiRequest("POST", "/api/claims/upload-to-cloud", formData);
const data = await res.json();
return (data.data ?? []) as ClaimFileMeta[];
};

View File

@@ -135,7 +135,7 @@ export function ChatbotButton() {
const [eligibilityData, setEligibilityData] = useState<EligibilityData | null>(null);
const [freeTextInput, setFreeTextInput] = useState("");
const [patientResult, setPatientResult] = useState<PatientResult | null>(null);
const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null } | null>(null);
const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null);
const [checkAndClaimData, setCheckAndClaimData] = useState<CheckAndClaimData | null>(null);
const [clarificationData, setClarificationData] = useState<{ memberId: string; dob: string; patient: PatientResult | null; procedureNames: string[]; options: string[] } | null>(null);
const [apptSelectionData, setApptSelectionData] = useState<{
@@ -299,29 +299,32 @@ export function ChatbotButton() {
prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck);
};
const handleEligibilityAndAppointment = async () => {
const handleEligibilityAndAppointment = async (targetDate?: string) => {
if (!eligibilityIdData) return;
addMsg("user", "Check eligibility & add to today's schedule");
const dateLabel = targetDate
? new Date(targetDate + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
: "today";
addMsg("user", `Check eligibility & add to schedule (${dateLabel})`);
if (!eligibilityIdData.patient) {
// Patient not yet in DB — eligibility check will create them; flag for post-check appointment
addMsg("bot", "Running eligibility check — will add patient and create today's appointment after...");
sessionStorage.setItem("chatbot_appt_after_eligibility", JSON.stringify({ memberId: eligibilityIdData.memberId }));
addMsg("bot", `Running eligibility check will add patient and create appointment for ${dateLabel} after...`);
sessionStorage.setItem("chatbot_appt_after_eligibility", JSON.stringify({ memberId: eligibilityIdData.memberId, date: targetDate ?? null }));
prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck);
return;
}
addMsg("bot", "Creating appointment for today...", true);
addMsg("bot", `Creating appointment for ${dateLabel}...`, true);
try {
const res = await apiRequest("POST", "/api/ai/create-appointment-today", {
patientId: eligibilityIdData.patient.id,
date: targetDate ?? undefined,
});
const data = await res.json();
if (!res.ok) {
replaceLastMsg(data.message ?? "Could not create appointment.");
return;
}
replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) — opening eligibility check page...`);
replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) for ${data.dateLabel} — opening eligibility check page...`);
prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck);
} catch {
replaceLastMsg("Could not create appointment. Please try again.");
@@ -393,6 +396,7 @@ export function ChatbotButton() {
siteKey: data.actionData.siteKey,
autoCheck: data.actionData.autoCheck,
patient: data.actionData.patient ?? null,
appointmentDate: data.actionData.appointmentDate ?? null,
});
setStep("eligibility-id-ready");
return;
@@ -694,11 +698,22 @@ export function ChatbotButton() {
<Button
size="sm"
className="w-full h-8 text-xs bg-emerald-600 hover:bg-emerald-700 text-white"
onClick={handleEligibilityAndAppointment}
onClick={() => handleEligibilityAndAppointment()}
>
<Calendar className="h-3 w-3 mr-1" />
Eligibility &amp; Appointment
Eligibility &amp; Appointment Today
</Button>
{eligibilityIdData.appointmentDate && (
<Button
size="sm"
className="w-full h-8 text-xs bg-teal-600 hover:bg-teal-700 text-white"
onClick={() => handleEligibilityAndAppointment(eligibilityIdData!.appointmentDate!)}
>
<Calendar className="h-3 w-3 mr-1" />
Eligibility &amp; Appointment on{" "}
{new Date(eligibilityIdData.appointmentDate + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
</Button>
)}
<Button size="sm" variant="ghost" className="w-full h-8 text-xs" onClick={reset}>
Cancel
</Button>
@@ -780,6 +795,7 @@ export function ChatbotButton() {
siteKey: data.actionData.siteKey,
autoCheck: data.actionData.autoCheck,
patient: data.actionData.patient ?? null,
appointmentDate: data.actionData.appointmentDate ?? null,
});
setStep("eligibility-id-ready");
} else {

View File

@@ -181,6 +181,16 @@ export default function AppointmentsPage() {
reply: string;
} | null>(null);
const [needsAiClaimResume, setNeedsAiClaimResume] = useState(false);
const [aiClaimCdtClarification, setAiClaimCdtClarification] = useState<{
unknownPhrases: string[];
codeInputs: Record<string, string>;
originalMessage: string;
aptDate: string;
appointmentId: number;
patientName: string;
notes: string;
matchedSoFar: Array<{ code: string; description: string }>;
} | null>(null);
const [selectedReminderColumns, setSelectedReminderColumns] = useState<Set<number>>(new Set());
const [isSendingReminders, setIsSendingReminders] = useState(false);
const [reminderAiFollowUp, setReminderAiFollowUp] = useState(true);
@@ -1413,6 +1423,20 @@ export default function AppointmentsPage() {
notes: apt.notes ?? "",
reply: data.reply ?? "",
});
} else if (data.action === "need_cdt_clarification" && data.actionData) {
const phrases: string[] = data.actionData.unknownPhrases ?? [];
const inputs: Record<string, string> = {};
for (const p of phrases) inputs[p] = "";
setAiClaimCdtClarification({
unknownPhrases: phrases,
codeInputs: inputs,
originalMessage: `claim ${apt.notes} for ${apt.patientName}`,
aptDate,
appointmentId: Number(apt.id),
patientName: apt.patientName,
notes: apt.notes ?? "",
matchedSoFar: data.actionData.matchedSoFar ?? [],
});
} else {
setAiClaimCurrentData({
matchedCodes: [],
@@ -1484,11 +1508,78 @@ export default function AppointmentsPage() {
};
const handleAiClaimSkip = async () => {
setAiClaimCdtClarification(null);
const nextIndex = aiClaimCurrentIndex + 1;
setAiClaimCurrentIndex(nextIndex);
await processAiClaimAtIndex(aiClaimQueue, nextIndex);
};
const handleAiClaimCdtSubmit = async () => {
if (!aiClaimCdtClarification) return;
const { codeInputs, originalMessage, aptDate, appointmentId, patientName, notes, matchedSoFar } = aiClaimCdtClarification;
setAiClaimCdtClarification(null);
setIsAiClaimProcessing(true);
setAiClaimCurrentData(null);
try {
for (const [phrase, code] of Object.entries(codeInputs)) {
await apiRequest("POST", "/api/ai/cdt-aliases/add", { phrase, cdtCode: code.trim() });
}
const res = await apiRequest("POST", "/api/ai/internal-chat", {
message: originalMessage,
clientDate: aptDate,
});
const data = await res.json();
if ((data.action === "claim_only_ready" || data.action === "check_and_claim_ready") && data.actionData) {
setAiClaimCurrentData({
matchedCodes: data.actionData.matchedCodes ?? [],
siteKey: data.actionData.siteKey ?? "",
serviceDate: data.actionData.serviceDate ?? aptDate,
appointmentId,
patientName,
notes,
reply: data.reply ?? "",
});
} else if (data.action === "need_cdt_clarification" && data.actionData) {
// Still has unknowns — loop again
const phrases: string[] = data.actionData.unknownPhrases ?? [];
const inputs: Record<string, string> = {};
for (const p of phrases) inputs[p] = "";
setAiClaimCdtClarification({
unknownPhrases: phrases,
codeInputs: inputs,
originalMessage,
aptDate,
appointmentId,
patientName,
notes,
matchedSoFar: data.actionData.matchedSoFar ?? matchedSoFar,
});
} else {
setAiClaimCurrentData({
matchedCodes: matchedSoFar.map((c) => ({ ...c, toothNumber: undefined, toothSurface: undefined })),
siteKey: "",
serviceDate: aptDate,
appointmentId,
patientName,
notes,
reply: data.reply ?? "Could not fully interpret notes.",
});
}
} catch {
setAiClaimCurrentData({
matchedCodes: matchedSoFar.map((c) => ({ ...c, toothNumber: undefined, toothSurface: undefined })),
siteKey: "",
serviceDate: aptDate,
appointmentId,
patientName,
notes,
reply: "Error retrying after alias save.",
});
} finally {
setIsAiClaimProcessing(false);
}
};
return (
<div>
<SeleniumTaskBanner
@@ -2193,6 +2284,68 @@ export default function AppointmentsPage() {
<LoaderCircleIcon className="h-4 w-4 animate-spin text-teal-600" />
Interpreting notes with AI...
</div>
) : aiClaimCdtClarification ? (
<div className="space-y-3">
<div>
<p className="text-sm font-semibold">{aiClaimCdtClarification.patientName}</p>
<p className="text-xs text-gray-500 mt-0.5">
Notes: <span className="italic">{aiClaimCdtClarification.notes}</span>
</p>
</div>
<p className="text-xs font-medium text-amber-700">
Unknown term{aiClaimCdtClarification.unknownPhrases.length > 1 ? "s" : ""} enter CDT code{aiClaimCdtClarification.unknownPhrases.length > 1 ? "s" : ""}:
</p>
{aiClaimCdtClarification.matchedSoFar.length > 0 && (
<div className="bg-teal-50 border border-teal-200 rounded p-2 space-y-0.5">
<p className="text-[10px] text-teal-700 font-medium uppercase tracking-wide">Already matched</p>
{aiClaimCdtClarification.matchedSoFar.map((c) => (
<p key={c.code} className="text-xs">
<span className="font-semibold text-teal-800">{c.code}</span>
<span className="text-gray-600"> {c.description}</span>
</p>
))}
</div>
)}
<div className="space-y-2">
{aiClaimCdtClarification.unknownPhrases.map((phrase) => (
<div key={phrase} className="flex items-center gap-2">
<span className="text-xs text-gray-700 font-medium shrink-0">"{phrase}" </span>
<input
type="text"
placeholder="D0272"
value={aiClaimCdtClarification.codeInputs[phrase] ?? ""}
onChange={(e) =>
setAiClaimCdtClarification((prev) =>
prev ? { ...prev, codeInputs: { ...prev.codeInputs, [phrase]: e.target.value.toUpperCase() } } : prev
)
}
className="flex-1 rounded border border-amber-300 bg-white px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-amber-400"
/>
</div>
))}
</div>
<div className="flex gap-2 pt-1">
<button
className="flex-1 flex items-center justify-center gap-1 text-xs h-8 px-3 rounded bg-amber-600 hover:bg-amber-700 text-white font-medium transition-colors disabled:opacity-50"
disabled={Object.values(aiClaimCdtClarification.codeInputs).some((v) => !v.trim())}
onClick={handleAiClaimCdtSubmit}
>
Save &amp; Retry
</button>
<button
className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded border border-gray-300 hover:bg-gray-50 text-gray-700 font-medium transition-colors"
onClick={() => { setAiClaimCdtClarification(null); handleAiClaimSkip(); }}
>
Skip
</button>
<button
className="flex items-center justify-center gap-1 text-xs h-8 px-3 rounded hover:bg-gray-100 text-gray-500 transition-colors"
onClick={() => { setAiClaimCdtClarification(null); setAiClaimModalOpen(false); sessionStorage.removeItem("ai_claim_queue"); }}
>
Cancel All
</button>
</div>
</div>
) : aiClaimCurrentData ? (
<div className="space-y-3">
<div>

View File

@@ -720,7 +720,7 @@ export default function InsuranceStatusPage() {
try {
const raw = sessionStorage.getItem("chatbot_appt_after_eligibility");
if (!raw) return;
const { memberId: storedMemberId } = JSON.parse(raw);
const { memberId: storedMemberId, date: storedDate } = JSON.parse(raw);
sessionStorage.removeItem("chatbot_appt_after_eligibility");
if (!storedMemberId) return;
@@ -729,12 +729,15 @@ export default function InsuranceStatusPage() {
const patient = await lookupRes.json();
if (!patient?.id) return;
const apptRes = await apiRequest("POST", "/api/ai/create-appointment-today", { patientId: patient.id });
const apptRes = await apiRequest("POST", "/api/ai/create-appointment-today", { patientId: patient.id, date: storedDate ?? undefined });
const apptData = await apptRes.json();
if (apptRes.ok) {
const scheduledOn = storedDate
? new Date(storedDate + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
: "today";
toast({
title: "Appointment created",
description: `${patient.firstName ?? ""} ${patient.lastName ?? ""} added to today's schedule at ${apptData.startTime} (${apptData.column ?? "Column A"}).`.trim(),
description: `${patient.firstName ?? ""} ${patient.lastName ?? ""} added to schedule (${scheduledOn}) at ${apptData.startTime} (${apptData.column ?? "Column A"}).`.trim(),
});
} else {
toast({