fix: D0140 claim sync, schedule column prefill, multi-appt, DentaQuest OTP session
- Fix Express route ordering in appointments-procedures so /prefill-from-appointment is matched before /:id (D0140 and other codes now always reach the claim) - claim-form: always fetch AppointmentProcedure records when an existing claim loads so post-save procedure edits (e.g. adding D0140) are reflected immediately - appointments-page: replace sessionStorage with React state (newApptPrefill) for slot-click prefill so columns B-F correctly carry their staffId into the form - add-appointment-modal / appointment-form: thread prefillData prop; add NewAppointmentPrefill interface; useEffect applies values via setValue - appointments upsert: remove per-patient dedup so the same patient can have multiple appointments on the same day in the same column - DentaQuest / TuftsSCO: navigate to about:blank and minimize instead of quit_driver after each run — session cookie stays in memory so OTP is only required once per app startup, not on every eligibility or claim check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
formatDateToHumanReadable,
|
||||
} from "@/utils/dateUtils";
|
||||
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
|
||||
import type { NewAppointmentPrefill } from "@/components/appointments/appointment-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Calendar as CalendarIcon,
|
||||
@@ -177,6 +178,7 @@ export default function AppointmentsPage() {
|
||||
timeSlot: TimeSlot;
|
||||
staffId: number;
|
||||
} | null>(null);
|
||||
const [newApptPrefill, setNewApptPrefill] = useState<NewAppointmentPrefill | null>(null);
|
||||
|
||||
const toggleReminderColumn = (staffId: number) => {
|
||||
setSelectedReminderColumns((prev) => {
|
||||
@@ -366,53 +368,21 @@ export default function AppointmentsPage() {
|
||||
|
||||
// Check for newPatient parameter in URL
|
||||
useEffect(() => {
|
||||
// Parse URL search params to check for newPatient
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const newPatientId = params.get("newPatient");
|
||||
|
||||
if (newPatientId) {
|
||||
const patientId = parseInt(newPatientId);
|
||||
// Choose first available staff safely (fallback to 1 if none)
|
||||
const firstStaff =
|
||||
staffMembers && staffMembers.length > 0 ? staffMembers[0] : undefined;
|
||||
const firstStaff = staffMembers.length > 0 ? staffMembers[0] : undefined;
|
||||
const staffId = firstStaff ? Number(firstStaff.id) : 1;
|
||||
// Find first time slot today (9:00 AM is a common starting time)
|
||||
const defaultTimeSlot =
|
||||
timeSlots.find((slot) => slot.time === "09:00") || timeSlots[0];
|
||||
const defaultTimeSlot = timeSlots.find((slot) => slot.time === "09:00") || timeSlots[0];
|
||||
if (!defaultTimeSlot) {
|
||||
toast({
|
||||
title: "Unable to schedule",
|
||||
description:
|
||||
"No available time slots to schedule the new patient right now.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast({ title: "Unable to schedule", description: "No available time slots.", variant: "destructive" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge any existing "newAppointmentData" with the patient info BEFORE opening modal
|
||||
try {
|
||||
const existingRaw = sessionStorage.getItem("newAppointmentData");
|
||||
const existing = existingRaw ? JSON.parse(existingRaw) : {};
|
||||
const newAppointmentData = {
|
||||
...existing,
|
||||
patientId: patientId,
|
||||
};
|
||||
sessionStorage.setItem(
|
||||
"newAppointmentData",
|
||||
JSON.stringify(newAppointmentData)
|
||||
);
|
||||
} catch (err) {
|
||||
// If sessionStorage parsing fails, overwrite with a fresh object
|
||||
sessionStorage.setItem(
|
||||
"newAppointmentData",
|
||||
JSON.stringify({ patientId: patientId })
|
||||
);
|
||||
}
|
||||
handleCreateAppointmentAtSlot(defaultTimeSlot, staffId, patientId);
|
||||
|
||||
// Open/create the appointment modal (will read sessionStorage in the modal)
|
||||
handleCreateAppointmentAtSlot(defaultTimeSlot, Number(staffId));
|
||||
|
||||
// Remove the query param from the URL so this doesn't re-run on navigation/refresh
|
||||
params.delete("newPatient");
|
||||
const newSearch = params.toString();
|
||||
const newUrl = `${window.location.pathname}${newSearch ? `?${newSearch}` : ""}${window.location.hash || ""}`;
|
||||
@@ -671,9 +641,9 @@ export default function AppointmentsPage() {
|
||||
// Handle creating a new appointment at a specific time slot and for a specific staff member
|
||||
const handleCreateAppointmentAtSlot = (
|
||||
timeSlot: TimeSlot,
|
||||
staffId: number
|
||||
staffId: number,
|
||||
patientId?: number
|
||||
) => {
|
||||
// Calculate end time (30 minutes after start time)
|
||||
const startHour = parseInt(timeSlot.time.split(":")[0] as string);
|
||||
const startMinute = parseInt(timeSlot.time.split(":")[1] as string);
|
||||
const startDate = parseLocalDate(selectedDate);
|
||||
@@ -682,59 +652,17 @@ export default function AppointmentsPage() {
|
||||
const endDate = addMinutes(startDate, 30);
|
||||
const endTime = `${endDate.getHours().toString().padStart(2, "0")}:${endDate.getMinutes().toString().padStart(2, "0")}`;
|
||||
|
||||
// Find staff member
|
||||
const staff = staffMembers.find((s) => Number(s.id) === Number(staffId));
|
||||
|
||||
// Try to read any existing prefill data (may include patientId from the URL handler)
|
||||
let existingStored: any = null;
|
||||
try {
|
||||
const raw = sessionStorage.getItem("newAppointmentData");
|
||||
existingStored = raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
// ignore parse errors and treat as no existing stored data
|
||||
existingStored = null;
|
||||
}
|
||||
|
||||
// Build the prefill appointment object and merge existing stored data
|
||||
const newAppointment = {
|
||||
// base defaults
|
||||
setNewApptPrefill({
|
||||
staffId: Number(staffId),
|
||||
date: formatLocalDate(selectedDate),
|
||||
startTime: timeSlot.time, // This is in "HH:MM" format
|
||||
endTime: endTime,
|
||||
startTime: timeSlot.time,
|
||||
endTime,
|
||||
type: staff?.role === "doctor" ? "checkup" : "cleaning",
|
||||
status: "scheduled",
|
||||
title: `Appointment with ${staff?.name}`,
|
||||
notes: `Appointment with ${staff?.name}`,
|
||||
staff: Number(staffId), // consistent field name that matches update mutation
|
||||
// if existingStored has patientId (or other fields) merge them below
|
||||
...(existingStored || {}),
|
||||
};
|
||||
|
||||
// Ensure explicit values from this function override stale values from storage
|
||||
// (for example, prefer current slot and staff)
|
||||
const mergedAppointment = {
|
||||
...newAppointment,
|
||||
date: newAppointment.date,
|
||||
startTime: newAppointment.startTime,
|
||||
endTime: newAppointment.endTime,
|
||||
staff: Number(staffId),
|
||||
};
|
||||
|
||||
// Persist merged prefill so the modal/form can read it
|
||||
try {
|
||||
sessionStorage.setItem(
|
||||
"newAppointmentData",
|
||||
JSON.stringify(mergedAppointment)
|
||||
);
|
||||
} catch (e) {
|
||||
// ignore sessionStorage write failures
|
||||
console.error("Failed to write newAppointmentData to sessionStorage", e);
|
||||
}
|
||||
|
||||
// For new appointments, set editingAppointment to undefined
|
||||
patientId,
|
||||
});
|
||||
setEditingAppointment(undefined);
|
||||
|
||||
// Open modal
|
||||
setIsAddModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -1898,13 +1826,17 @@ export default function AppointmentsPage() {
|
||||
{/* Add/Edit Appointment Modal */}
|
||||
<AddAppointmentModal
|
||||
open={isAddModalOpen}
|
||||
onOpenChange={setIsAddModalOpen}
|
||||
onOpenChange={(open) => {
|
||||
setIsAddModalOpen(open);
|
||||
if (!open) setNewApptPrefill(null);
|
||||
}}
|
||||
onSubmit={handleAppointmentSubmit}
|
||||
isLoading={
|
||||
createAppointmentMutation.isPending ||
|
||||
updateAppointmentMutation.isPending
|
||||
}
|
||||
appointment={editingAppointment}
|
||||
prefillData={editingAppointment ? null : newApptPrefill}
|
||||
onDelete={handleDeleteAppointment}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user