From af1b0f8d7029ab2efc27ee86e8151d1ea0f09036 Mon Sep 17 00:00:00 2001 From: Potenz Date: Mon, 28 Jul 2025 00:11:29 +0530 Subject: [PATCH] appointment creation fixed --- apps/Backend/src/routes/appointements.ts | 202 +++++---- apps/Backend/src/storage/index.ts | 87 ++++ .../components/claims/claim-form-handler.tsx | 409 ++++++++++++++++++ .../src/components/claims/claim-form.tsx | 82 ++-- apps/Frontend/src/pages/appointments-page.tsx | 4 +- apps/Frontend/src/pages/claims-page.tsx | 118 +++-- apps/Frontend/src/pages/dashboard.tsx | 4 +- 7 files changed, 696 insertions(+), 210 deletions(-) create mode 100644 apps/Frontend/src/components/claims/claim-form-handler.tsx diff --git a/apps/Backend/src/routes/appointements.ts b/apps/Backend/src/routes/appointements.ts index 07e374d..64c6e83 100644 --- a/apps/Backend/src/routes/appointements.ts +++ b/apps/Backend/src/routes/appointements.ts @@ -172,7 +172,7 @@ router.get("/appointments/recent", async (req: Request, res: Response) => { // Create a new appointment router.post( - "/", + "/upsert", async (req: Request, res: Response): Promise => { try { @@ -182,53 +182,55 @@ router.post( userId: req.user!.id, }); - // Verify patient exists and belongs to user + const userId = req.user!.id; + + // 1. Verify patient exists and belongs to user const patient = await storage.getPatient(appointmentData.patientId); if (!patient) { - console.log("Patient not found:", appointmentData.patientId); return res.status(404).json({ message: "Patient not found" }); } - if (patient.userId !== req.user!.id) { - console.log( - "Patient belongs to another user. Patient userId:", - patient.userId, - "Request userId:", - req.user!.id - ); - return res.status(403).json({ message: "Forbidden" }); - } - - // Check if there's already an appointment at this time slot - const existingAppointments = await storage.getAppointmentsByUserId( - req.user!.id - ); - const conflictingAppointment = existingAppointments.find( - (apt) => - apt.date === appointmentData.date && - apt.startTime === appointmentData.startTime && - apt.notes?.includes( - appointmentData.notes.split("Appointment with ")[1] - ) - ); - - if (conflictingAppointment) { - console.log( - "Time slot already booked:", - appointmentData.date, - appointmentData.startTime - ); - return res.status(409).json({ - message: - "This time slot is already booked. Please select another time or staff member.", + if (patient.userId !== userId) { + return res.status(403).json({ + message: "Forbidden, You are not the user who created this patient.", }); } - // Create appointment - const appointment = await storage.createAppointment(appointmentData); - res.status(201).json(appointment); + // 2. Check if patient already has an appointment on the same date and time. + const sameDayAppointment = await storage.getPatientAppointmentByDateTime( + appointmentData.patientId, + appointmentData.date, + appointmentData.startTime + ); + // 3. Check if there's already an appointment at this time slot of Staff. + const staffConflict = await storage.getStaffAppointmentByDateTime( + appointmentData.staffId, + appointmentData.date, + appointmentData.startTime, + sameDayAppointment?.id + ); + + if (staffConflict) { + return res.status(409).json({ + message: + "This time slot is already booked for the selected staff. Please choose another time or staff member.", + }); + } + + // 4. If same-day appointment exists, update it + if (sameDayAppointment?.id !== undefined) { + const updatedAppointment = await storage.updateAppointment( + sameDayAppointment.id, + appointmentData + ); + return res.status(200).json(updatedAppointment); + } + + // 6. Otherwise, create a new appointment + const newAppointment = await storage.createAppointment(appointmentData); + return res.status(201).json(newAppointment); } catch (error) { - console.error("Error creating appointment:", error); + console.error("Error in upsert appointment:", error); if (error instanceof z.ZodError) { console.log( @@ -242,7 +244,7 @@ router.post( } res.status(500).json({ - message: "Failed to create appointment", + message: "Failed to upsert appointment", error: error instanceof Error ? error.message : String(error), }); } @@ -255,89 +257,85 @@ router.put( async (req: Request, res: Response): Promise => { try { + const appointmentData = updateAppointmentSchema.parse({ + ...req.body, + userId: req.user!.id, + }); + + const userId = req.user!.id; + const appointmentIdParam = req.params.id; if (!appointmentIdParam) { return res.status(400).json({ message: "Appointment ID is required" }); } const appointmentId = parseInt(appointmentIdParam); - // Check if appointment exists and belongs to user + // 1. Verify patient exists and belongs to user + const patient = await storage.getPatient(appointmentData.patientId); + if (!patient) { + return res.status(404).json({ message: "Patient not found" }); + } + + if (patient.userId !== userId) { + return res.status(403).json({ + message: "Forbidden, You are not the user who created this patient.", + }); + } + + // 2. Check if appointment exists and belongs to user const existingAppointment = await storage.getAppointment(appointmentId); if (!existingAppointment) { console.log("Appointment not found:", appointmentId); return res.status(404).json({ message: "Appointment not found" }); } - if (existingAppointment.userId !== req.user!.id) { - console.log( - "Appointment belongs to another user. Appointment userId:", - existingAppointment.userId, - "Request userId:", - req.user!.id - ); - return res.status(403).json({ message: "Forbidden" }); + return res.status(403).json({ + message: + "Forbidden, You are not the user who created this appointment.", + }); } - // Validate request body - const appointmentData = updateAppointmentSchema.parse(req.body); - - // If patient ID is being updated, verify the new patient belongs to user + // 4. Reject patientId change (not allowed) if ( appointmentData.patientId && appointmentData.patientId !== existingAppointment.patientId ) { - const patient = await storage.getPatient(appointmentData.patientId); - if (!patient) { - console.log("New patient not found:", appointmentData.patientId); - return res.status(404).json({ message: "Patient not found" }); - } - - if (patient.userId !== req.user!.id) { - console.log( - "New patient belongs to another user. Patient userId:", - patient.userId, - "Request userId:", - req.user!.id - ); - return res.status(403).json({ message: "Forbidden" }); - } + return res + .status(400) + .json({ message: "Changing patientId is not allowed" }); } - // Check if there's already an appointment at this time slot (if time is being changed) - if ( - appointmentData.date && - appointmentData.startTime && - (appointmentData.date !== existingAppointment.date || - appointmentData.startTime !== existingAppointment.startTime) - ) { - // Extract staff name from notes - const staffInfo = - appointmentData.notes?.split("Appointment with ")[1] || - existingAppointment.notes?.split("Appointment with ")[1]; + // 5. Check for conflicting appointments (same patient OR staff at same time) - const existingAppointments = await storage.getAppointmentsByUserId( - req.user!.id - ); - const conflictingAppointment = existingAppointments.find( - (apt) => - apt.id !== appointmentId && // Don't match with itself - apt.date === (appointmentData.date || existingAppointment.date) && - apt.startTime === - (appointmentData.startTime || existingAppointment.startTime) && - apt.notes?.includes(staffInfo) - ); + const date = appointmentData.date ?? existingAppointment.date; + const startTime = + appointmentData.startTime ?? existingAppointment.startTime; + const staffId = appointmentData.staffId ?? existingAppointment.staffId; - if (conflictingAppointment) { - console.log( - "Time slot already booked:", - appointmentData.date, - appointmentData.startTime - ); - return res.status(409).json({ - message: - "This time slot is already booked. Please select another time or staff member.", - }); - } + const patientConflict = await storage.getPatientConflictAppointment( + existingAppointment.patientId, + date, + startTime, + appointmentId + ); + + if (patientConflict) { + return res.status(409).json({ + message: "This patient already has an appointment at this time.", + }); + } + + const staffConflict = await storage.getStaffConflictAppointment( + staffId, + date, + startTime, + appointmentId + ); + + if (staffConflict) { + return res.status(409).json({ + message: "This time slot is already booked for the selected staff.", + }); } // Update appointment @@ -345,7 +343,7 @@ router.put( appointmentId, appointmentData ); - res.json(updatedAppointment); + return res.json(updatedAppointment); } catch (error) { console.error("Error updating appointment:", error); diff --git a/apps/Backend/src/storage/index.ts b/apps/Backend/src/storage/index.ts index a649e58..0d64007 100644 --- a/apps/Backend/src/storage/index.ts +++ b/apps/Backend/src/storage/index.ts @@ -209,6 +209,29 @@ export interface IStorage { appointment: UpdateAppointment ): Promise; deleteAppointment(id: number): Promise; + getPatientAppointmentByDateTime( + patientId: number, + date: Date, + startTime: string + ): Promise; + getStaffAppointmentByDateTime( + staffId: number, + date: Date, + startTime: string, + excludeId?: number + ): Promise; + getPatientConflictAppointment( + patientId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise; + getStaffConflictAppointment( + staffId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise; // Staff methods getStaff(id: number): Promise; @@ -483,6 +506,70 @@ export const storage: IStorage = { } }, + async getPatientAppointmentByDateTime( + patientId: number, + date: Date, + startTime: string + ): Promise { + return await db.appointment.findFirst({ + where: { + patientId, + date, + startTime, + }, + }) ?? undefined; + }, + + async getStaffAppointmentByDateTime( + staffId: number, + date: Date, + startTime: string, + excludeId?: number + ): Promise { + return await db.appointment.findFirst({ + where: { + staffId, + date, + startTime, + NOT: excludeId ? { id: excludeId } : undefined, + }, + }) ?? undefined; + }, + + async getPatientConflictAppointment( + patientId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise { + return await db.appointment.findFirst({ + where: { + patientId, + date, + startTime, + NOT: { id: excludeId }, + }, + }) ?? undefined; + }, + + async getStaffConflictAppointment( + staffId: number, + date: Date, + startTime: string, + excludeId: number + ): Promise { + return await db.appointment.findFirst({ + where: { + staffId, + date, + startTime, + NOT: { id: excludeId }, + }, + }) ?? undefined; + }, + + + // Staff methods async getStaff(id: number): Promise { const staff = await db.staff.findUnique({ where: { id } }); return staff ?? undefined; diff --git a/apps/Frontend/src/components/claims/claim-form-handler.tsx b/apps/Frontend/src/components/claims/claim-form-handler.tsx new file mode 100644 index 0000000..048c217 --- /dev/null +++ b/apps/Frontend/src/components/claims/claim-form-handler.tsx @@ -0,0 +1,409 @@ +import { AppDispatch } from "@/redux/store"; +import { useState, useEffect, useMemo } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { TopAppBar } from "@/components/layout/top-app-bar"; +import { Sidebar } from "@/components/layout/sidebar"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/components/ui/card"; +import { ClaimForm } from "@/components/claims/claim-form"; +import { useToast } from "@/hooks/use-toast"; +import { useAuth } from "@/hooks/use-auth"; +import { + PatientUncheckedCreateInputObjectSchema, + AppointmentUncheckedCreateInputObjectSchema, + ClaimUncheckedCreateInputObjectSchema, +} from "@repo/db/usedSchemas"; +import { FileCheck } from "lucide-react"; +import { parse } from "date-fns"; +import { z } from "zod"; +import { apiRequest, queryClient } from "@/lib/queryClient"; +import { useLocation } from "wouter"; +import { useAppDispatch, useAppSelector } from "@/redux/hooks"; +import { + setTaskStatus, + clearTaskStatus, +} from "@/redux/slices/seleniumClaimSubmitTaskSlice"; +import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner"; +import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; +import ClaimsRecentTable from "@/components/claims/claims-recent-table"; +import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table"; + +//creating types out of schema auto generated. +type Appointment = z.infer; +type Claim = z.infer; + +const insertAppointmentSchema = ( + AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + id: true, + createdAt: true, +}); +type InsertAppointment = z.infer; + +const updateAppointmentSchema = ( + AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject +) + .omit({ + id: true, + createdAt: true, + }) + .partial(); +type UpdateAppointment = z.infer; + +const PatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + appointments: true, +}); +type Patient = z.infer; + +const insertPatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +).omit({ + id: true, + createdAt: true, +}); +type InsertPatient = z.infer; + +const updatePatientSchema = ( + PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject +) + .omit({ + id: true, + createdAt: true, + userId: true, + }) + .partial(); + +type UpdatePatient = z.infer; + +const { toast } = useToast(); + +// Add patient mutation +const addPatientMutation = useMutation({ + mutationFn: async (patient: InsertPatient) => { + const res = await apiRequest("POST", "/api/patients/", patient); + return res.json(); + }, + onSuccess: (newPatient) => { + queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); + toast({ + title: "Success", + description: "Patient added successfully!", + variant: "default", + }); + }, + onError: (error) => { + toast({ + title: "Error", + description: `Failed to add patient: ${error.message}`, + variant: "destructive", + }); + }, +}); + +// Update patient mutation +const updatePatientMutation = useMutation({ + mutationFn: async ({ + id, + patient, + }: { + id: number; + patient: UpdatePatient; + }) => { + const res = await apiRequest("PUT", `/api/patients/${id}`, patient); + return res.json(); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); + toast({ + title: "Success", + description: "Patient updated successfully!", + variant: "default", + }); + }, + onError: (error) => { + toast({ + title: "Error", + description: `Failed to update patient: ${error.message}`, + variant: "destructive", + }); + }, +}); + +// Create/upsert appointment mutation +const createAppointmentMutation = useMutation({ + mutationFn: async (appointment: InsertAppointment) => { + const res = await apiRequest("POST", "/api/appointments/upsert", appointment); + return await res.json(); + }, + onSuccess: () => { + toast({ + title: "Success", + description: "Appointment created successfully.", + }); + // Invalidate both appointments and patients queries + queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] }); + queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: `Failed to create appointment: ${error.message}`, + variant: "destructive", + }); + }, +}); + +// Update appointment mutation +const updateAppointmentMutation = useMutation({ + mutationFn: async ({ + id, + appointment, + }: { + id: number; + appointment: UpdateAppointment; + }) => { + const res = await apiRequest("PUT", `/api/appointments/${id}`, appointment); + return await res.json(); + }, + onSuccess: () => { + toast({ + title: "Success", + description: "Appointment updated successfully.", + }); + queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] }); + queryClient.invalidateQueries({ queryKey: ["/api/patients/"] }); + }, + onError: (error: Error) => { + toast({ + title: "Error", + description: `Failed to update appointment: ${error.message}`, + variant: "destructive", + }); + }, +}); + +const createClaimMutation = useMutation({ + mutationFn: async (claimData: any) => { + const res = await apiRequest("POST", "/api/claims/", claimData); + return res.json(); + }, + onSuccess: () => { + toast({ + title: "Claim created successfully", + variant: "default", + }); + }, + onError: (error: any) => { + toast({ + title: "Error submitting claim", + description: error.message, + variant: "destructive", + }); + }, +}); + +function handleClaimSubmit(claimData: any): Promise { + return createClaimMutation.mutateAsync(claimData).then((data) => { + return data; + }); +} + +const handleAppointmentSubmit = async ( + appointmentData: InsertAppointment | UpdateAppointment +): Promise => { + const rawDate = parseLocalDate(appointmentData.date); + const formattedDate = formatLocalDate(rawDate); + + // Prepare minimal data to update/create + const minimalData = { + date: rawDate.toLocaleDateString("en-CA"), // "YYYY-MM-DD" format + startTime: appointmentData.startTime || "09:00", + endTime: appointmentData.endTime || "09:30", + staffId: appointmentData.staffId, + }; + + // Find existing appointment for this patient on the same date + const existingAppointment = appointments.find( + (a) => + a.patientId === appointmentData.patientId && + formatLocalDate(parseLocalDate(a.date)) === formattedDate + ); + + if (existingAppointment && typeof existingAppointment.id === "number") { + // Update appointment with only date + updateAppointmentMutation.mutate({ + id: existingAppointment.id, + appointment: minimalData, + }); + return existingAppointment.id; + } + + return new Promise((resolve, reject) => { + createAppointmentMutation.mutate( + { + ...minimalData, + patientId: appointmentData.patientId, + userId: user?.id, + title: "Scheduled Appointment", + type: "checkup", + }, + { + onSuccess: (newAppointment) => { + resolve(newAppointment.id); + }, + onError: (error) => { + toast({ + title: "Error", + description: "Could not create appointment", + variant: "destructive", + }); + reject(error); + }, + } + ); + }); +}; + +// Update Patient ( for insuranceId and Insurance Provider) +const handleUpdatePatient = (patient: UpdatePatient & { id?: number }) => { + if (patient) { + const { id, ...sanitizedPatient } = patient; + updatePatientMutation.mutate({ + id: Number(patient.id), + patient: sanitizedPatient, + }); + } else { + console.error("No current patient or user found for update"); + toast({ + title: "Error", + description: "Cannot update patient: No patient or user found", + variant: "destructive", + }); + } +}; + +// handle selenium sybmiting Mass Health claim +export const handleMHClaimSubmitSelenium = async ( + data: any, + dispatch: AppDispatch, + selectedPatient: number | null +) => { + const formData = new FormData(); + formData.append("data", JSON.stringify(data)); + const uploadedFiles: File[] = data.uploadedFiles ?? []; + + uploadedFiles.forEach((file: File) => { + if (file.type === "application/pdf") { + formData.append("pdfs", file); + } else if (file.type.startsWith("image/")) { + formData.append("images", file); + } + }); + + try { + dispatch( + setTaskStatus({ + status: "pending", + message: "Submitting claim to Selenium...", + }) + ); + const response = await apiRequest("POST", "/api/claims/selenium", formData); + const result1 = await response.json(); + if (result1.error) throw new Error(result1.error); + + dispatch( + setTaskStatus({ + status: "pending", + message: "Submitted to Selenium. Awaiting PDF...", + }) + ); + + toast({ + title: "Selenium service notified", + description: + "Your claim data was successfully sent to Selenium, Waitinig for its response.", + variant: "default", + }); + + const result2 = await handleSeleniumPdfDownload( + result1, + dispatch, + selectedPatient + ); + return result2; + } catch (error: any) { + dispatch( + setTaskStatus({ + status: "error", + message: error.message || "Selenium submission failed", + }) + ); + toast({ + title: "Selenium service error", + description: error.message || "An error occurred.", + variant: "destructive", + }); + } +}; + +// selenium pdf download handler +export const handleSeleniumPdfDownload = async ( + data: any, + dispatch: AppDispatch, + selectedPatient: number | null +) => { + try { + if (!selectedPatient) { + throw new Error("Missing patientId"); + } + + dispatch( + setTaskStatus({ + status: "pending", + message: "Downloading PDF from Selenium...", + }) + ); + + const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", { + patientId: selectedPatient, + pdf_url: data.pdf_url, + }); + const result = await res.json(); + if (result.error) throw new Error(result.error); + + dispatch( + setTaskStatus({ + status: "success", + message: "Claim submitted & PDF downloaded successfully.", + }) + ); + + toast({ + title: "Success", + description: "Claim Submitted and Pdf Downloaded completed.", + }); + + // setSelectedPatient(null); + + return result; + } catch (error: any) { + dispatch( + setTaskStatus({ + status: "error", + message: error.message || "Failed to download PDF", + }) + ); + toast({ + title: "Error", + description: error.message || "Failed to fetch PDF", + variant: "destructive", + }); + } +}; diff --git a/apps/Frontend/src/components/claims/claim-form.tsx b/apps/Frontend/src/components/claims/claim-form.tsx index d8797ac..d585086 100644 --- a/apps/Frontend/src/components/claims/claim-form.tsx +++ b/apps/Frontend/src/components/claims/claim-form.tsx @@ -22,6 +22,8 @@ import { PatientUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema, ClaimUncheckedCreateInputObjectSchema, + ClaimStatusSchema, + StaffUncheckedCreateInputObjectSchema, } from "@repo/db/usedSchemas"; import { z } from "zod"; import { useQuery } from "@tanstack/react-query"; @@ -31,7 +33,6 @@ import { useAuth } from "@/hooks/use-auth"; import { Tooltip, TooltipContent, - TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import procedureCodes from "../../assets/data/procedureCodes.json"; @@ -57,7 +58,6 @@ const updatePatientSchema = ( type UpdatePatient = z.infer; //creating types out of schema auto generated. -type Appointment = z.infer; const insertAppointmentSchema = ( AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject @@ -114,35 +114,31 @@ interface ClaimFormProps { appointmentData: InsertAppointment | UpdateAppointment ) => void; onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void; - onHandleForSelenium: (data: ClaimFormData) => void; + onHandleForMHSelenium: (data: ClaimFormData) => void; onClose: () => void; } -interface Staff { - id: string; - name: string; - role: "doctor" | "hygienist"; - color: string; -} +export type ClaimStatus = z.infer; +type Staff = z.infer; export function ClaimForm({ patientId, extractedData, onHandleAppointmentSubmit, onHandleUpdatePatient, - onHandleForSelenium, + onHandleForMHSelenium, onSubmit, onClose, }: ClaimFormProps) { const { toast } = useToast(); const { user } = useAuth(); - // Patient state - initialize from extractedData or null (new patient) + // Patient state - initialize from extractedData (if given ) or null (new patient) const [patient, setPatient] = useState( extractedData ? ({ ...extractedData } as Patient) : null ); - // Query patient + // Query patient based on given patient id const { data: fetchedPatient, isLoading, @@ -189,7 +185,6 @@ export function ClaimForm({ const [serviceDate, setServiceDate] = useState( formatLocalDate(new Date()) ); - useEffect(() => { if (extractedData?.serviceDate) { const parsed = parseLocalDate(extractedData.serviceDate); @@ -209,6 +204,21 @@ export function ClaimForm({ } }; + // when service date is chenged, it will change the each service lines procedure date in sync as well. + useEffect(() => { + setForm((prevForm) => { + const updatedLines = prevForm.serviceLines.map((line) => ({ + ...line, + procedureDate: serviceDate, // set all to current serviceDate string + })); + return { + ...prevForm, + serviceLines: updatedLines, + serviceDate, // keep form.serviceDate in sync as well + }; + }); + }, [serviceDate]); + // Determine patient date of birth format const formatDOB = (dob: string | undefined) => { if (!dob) return ""; @@ -262,20 +272,6 @@ export function ClaimForm({ } }, [patient]); - useEffect(() => { - setForm((prevForm) => { - const updatedLines = prevForm.serviceLines.map((line) => ({ - ...line, - procedureDate: serviceDate, // set all to current serviceDate string - })); - return { - ...prevForm, - serviceLines: updatedLines, - serviceDate, // keep form.serviceDate in sync as well - }; - }); - }, [serviceDate]); - // Handle patient field changes (to make inputs controlled and editable) const updatePatientField = (field: keyof Patient, value: any) => { setPatient((prev) => (prev ? { ...prev, [field]: value } : null)); @@ -362,15 +358,14 @@ export function ClaimForm({ setIsUploading(false); }; - // Mass Health Button Handler + // 1st Button workflow - Mass Health Button Handler const handleMHSubmit = async () => { + // 1. Create or update appointment const appointmentData = { patientId: patientId, date: serviceDate, staffId: staff?.id, }; - - // 1. Create or update appointment const appointmentId = await onHandleAppointmentSubmit(appointmentData); // 2. Update patient @@ -395,7 +390,6 @@ export function ClaimForm({ const filteredServiceLines = form.serviceLines.filter( (line) => line.procedureCode.trim() !== "" ); - const { uploadedFiles, insuranceSiteKey, ...formToCreateClaim } = form; const createdClaim = await onSubmit({ ...formToCreateClaim, @@ -407,7 +401,7 @@ export function ClaimForm({ }); // 4. sending form data to selenium service - onHandleForSelenium({ + onHandleForMHSelenium({ ...form, serviceLines: filteredServiceLines, staffId: Number(staff?.id), @@ -417,7 +411,8 @@ export function ClaimForm({ insuranceSiteKey: "MH", claimId: createdClaim.id, }); - // 4. Close form + + // 5. Close form onClose(); }; @@ -541,10 +536,10 @@ export function ClaimForm({ Treating Doctor diff --git a/apps/Frontend/src/pages/appointments-page.tsx b/apps/Frontend/src/pages/appointments-page.tsx index 3aa0a92..c882882 100644 --- a/apps/Frontend/src/pages/appointments-page.tsx +++ b/apps/Frontend/src/pages/appointments-page.tsx @@ -287,10 +287,10 @@ export default function AppointmentsPage() { } }, [patients, user, location]); - // Create appointment mutation + // Create/upsert appointment mutation const createAppointmentMutation = useMutation({ mutationFn: async (appointment: InsertAppointment) => { - const res = await apiRequest("POST", "/api/appointments/", appointment); + const res = await apiRequest("POST", "/api/appointments/upsert", appointment); return await res.json(); }, onSuccess: () => { diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index 2e03bbb..1bb6d19 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -2,7 +2,13 @@ import { useState, useEffect, useMemo } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { TopAppBar } from "@/components/layout/top-app-bar"; import { Sidebar } from "@/components/layout/sidebar"; -import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card"; +import { + Card, + CardHeader, + CardTitle, + CardContent, + CardDescription, +} from "@/components/ui/card"; import { ClaimForm } from "@/components/claims/claim-form"; import { useToast } from "@/hooks/use-toast"; import { useAuth } from "@/hooks/use-auth"; @@ -12,7 +18,7 @@ import { ClaimUncheckedCreateInputObjectSchema, } from "@repo/db/usedSchemas"; import { FileCheck } from "lucide-react"; -import { parse, format } from "date-fns"; +import { parse } from "date-fns"; import { z } from "zod"; import { apiRequest, queryClient } from "@/lib/queryClient"; import { useLocation } from "wouter"; @@ -78,7 +84,9 @@ type UpdatePatient = z.infer; export default function ClaimsPage() { const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isClaimFormOpen, setIsClaimFormOpen] = useState(false); - const [selectedPatient, setSelectedPatient] = useState(null); + const [selectedPatientId, setSelectedPatientId] = useState( + null + ); const dispatch = useAppDispatch(); const { status, message, show } = useAppSelector( (state) => state.seleniumClaimSubmitTask @@ -168,10 +176,14 @@ export default function ClaimsPage() { }, }); - // Create appointment mutation + // Create/upsert appointment mutation const createAppointmentMutation = useMutation({ mutationFn: async (appointment: InsertAppointment) => { - const res = await apiRequest("POST", "/api/appointments/", appointment); + const res = await apiRequest( + "POST", + "/api/appointments/upsert", + appointment + ); return await res.json(); }, onSuccess: () => { @@ -228,50 +240,29 @@ export default function ClaimsPage() { const handleAppointmentSubmit = async ( appointmentData: InsertAppointment | UpdateAppointment ): Promise => { - const rawDate = parseLocalDate(appointmentData.date); - const formattedDate = formatLocalDate(rawDate); - - // Prepare minimal data to update/create - const minimalData = { - date: rawDate.toLocaleDateString("en-CA"), // "YYYY-MM-DD" format - startTime: appointmentData.startTime || "09:00", - endTime: appointmentData.endTime || "09:30", - staffId: appointmentData.staffId, - }; - - // Find existing appointment for this patient on the same date - const existingAppointment = appointments.find( - (a) => - a.patientId === appointmentData.patientId && - formatLocalDate(parseLocalDate(a.date)) === formattedDate - ); - - if (existingAppointment && typeof existingAppointment.id === "number") { - // Update appointment with only date - updateAppointmentMutation.mutate({ - id: existingAppointment.id, - appointment: minimalData, - }); - return existingAppointment.id; - } - return new Promise((resolve, reject) => { + console.log("Constructed appointmentData:", appointmentData); + + console.log(appointmentData.date); createAppointmentMutation.mutate( { - ...minimalData, + date: appointmentData.date, + startTime: appointmentData.startTime || "09:00", + endTime: appointmentData.endTime || "09:30", + staffId: appointmentData.staffId, patientId: appointmentData.patientId, userId: user?.id, title: "Scheduled Appointment", type: "checkup", }, { - onSuccess: (newAppointment) => { - resolve(newAppointment.id); + onSuccess: (appointment) => { + resolve(appointment.id); }, onError: (error) => { toast({ title: "Error", - description: "Could not create appointment", + description: "Could not schedule appointment", variant: "destructive", }); reject(error); @@ -324,7 +315,7 @@ export default function ClaimsPage() { }; const handleNewClaim = (patientId: number) => { - setSelectedPatient(patientId); + setSelectedPatientId(patientId); setIsClaimFormOpen(true); const patient = patients.find((p) => p.id === patientId); @@ -334,6 +325,7 @@ export default function ClaimsPage() { }; const closeClaim = () => { + setSelectedPatientId(null); setIsClaimFormOpen(false); setClaimFormData({ patientId: null, @@ -379,7 +371,7 @@ export default function ClaimsPage() { ); if (matchingPatient) { - setSelectedPatient(matchingPatient.id); + setSelectedPatientId(matchingPatient.id); prefillClaimForm(matchingPatient); setIsClaimFormOpen(true); } else { @@ -401,7 +393,7 @@ export default function ClaimsPage() { addPatientMutation.mutate(newPatient, { onSuccess: (created) => { - setSelectedPatient(created.id); + setSelectedPatientId(created.id); prefillClaimForm(created); setIsClaimFormOpen(true); }, @@ -474,8 +466,8 @@ export default function ClaimsPage() { } }; - // handle selenium - const handleSelenium = async (data: any) => { + // handle selenium sybmiting Mass Health claim + const handleMHClaimSubmitSelenium = async (data: any) => { const formData = new FormData(); formData.append("data", JSON.stringify(data)); const uploadedFiles: File[] = data.uploadedFiles ?? []; @@ -517,7 +509,7 @@ export default function ClaimsPage() { variant: "default", }); - const result2 = await handleSeleniumPdfDownload(result1); + const result2 = await handleMHSeleniumPdfDownload(result1); return result2; } catch (error: any) { dispatch( @@ -535,9 +527,9 @@ export default function ClaimsPage() { }; // selenium pdf download handler - const handleSeleniumPdfDownload = async (data: any) => { + const handleMHSeleniumPdfDownload = async (data: any) => { try { - if (!selectedPatient) { + if (!selectedPatientId) { throw new Error("Missing patientId"); } @@ -549,7 +541,7 @@ export default function ClaimsPage() { ); const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", { - patientId: selectedPatient, + patientId: selectedPatientId, pdf_url: data.pdf_url, }); const result = await res.json(); @@ -567,8 +559,6 @@ export default function ClaimsPage() { description: "Claim Submitted and Pdf Downloaded completed.", }); - setSelectedPatient(null); - return result; } catch (error: any) { dispatch( @@ -705,20 +695,20 @@ export default function ClaimsPage() { {/* Recent Claims Section */} - - Recently Submitted Claims - - View and manage all recent claims information - - - - - - + + Recently Submitted Claims + + View and manage all recent claims information + + + + + + {/* Recent Claims by Patients */} @@ -726,15 +716,15 @@ export default function ClaimsPage() { {/* Claim Form Modal */} - {isClaimFormOpen && selectedPatient !== null && ( + {isClaimFormOpen && selectedPatientId !== null && ( )} diff --git a/apps/Frontend/src/pages/dashboard.tsx b/apps/Frontend/src/pages/dashboard.tsx index 77f737f..b0c7c70 100644 --- a/apps/Frontend/src/pages/dashboard.tsx +++ b/apps/Frontend/src/pages/dashboard.tsx @@ -153,10 +153,10 @@ export default function Dashboard() { const isLoading = isLoadingPatients || addPatientMutation.isPending; - // Create appointment mutation + // Create/upsert appointment mutation const createAppointmentMutation = useMutation({ mutationFn: async (appointment: InsertAppointment) => { - const res = await apiRequest("POST", "/api/appointments/", appointment); + const res = await apiRequest("POST", "/api/appointments/upsert", appointment); return await res.json(); }, onSuccess: () => {