claim page small fixes

This commit is contained in:
2025-07-28 18:12:26 +05:30
parent 0930b70bfd
commit b9b449a909
5 changed files with 2 additions and 532 deletions

View File

@@ -1,409 +0,0 @@
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<typeof AppointmentUncheckedCreateInputObjectSchema>;
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const insertPatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
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<Claim> {
return createClaimMutation.mutateAsync(claimData).then((data) => {
return data;
});
}
const handleAppointmentSubmit = async (
appointmentData: InsertAppointment | UpdateAppointment
): Promise<number> => {
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<number>((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",
});
}
};

View File

@@ -1,63 +0,0 @@
import { useState, useEffect } from "react";
import { ClaimForm } from "./claim-form";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import {z} from "zod";
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
interface ClaimModalProps {
open: boolean;
onClose: () => void;
patientId: number;
appointmentId: number;
}
export function ClaimModal({
open,
onClose,
patientId,
appointmentId
}: ClaimModalProps) {
const [patient, setPatient] = useState<Patient | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// Fetch patient data
const fetchPatient = async () => {
try {
setLoading(true);
const response = await fetch(`/api/patients/${patientId}`);
if (!response.ok) {
throw new Error("Failed to fetch patient data");
}
const data = await response.json();
setPatient(data);
} catch (error) {
console.error("Error fetching patient:", error);
} finally {
setLoading(false);
}
};
if (open && patientId) {
fetchPatient();
}
}, [patientId, open]);
if (!open) return null;
const patientName = patient ? `${patient.firstName} ${patient.lastName}` : `Patient #${patientId}`;
return (
<ClaimForm
patientId={patientId}
appointmentId={appointmentId}
patientName={patientName}
onClose={onClose}
patientData={patient || undefined}
/>
);
}

View File

@@ -1,10 +1,4 @@
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import ClaimsRecentTable from "./claims-recent-table";
import { PatientTable } from "../patients/patient-table";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";

View File

@@ -4,12 +4,10 @@ import { format, addDays, startOfToday, addMinutes } from "date-fns";
import {
parseLocalDate,
formatLocalDate,
normalizeToISOString,
} from "@/utils/dateUtils";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
import { ClaimModal } from "@/components/claims/claim-modal";
import { Button } from "@/components/ui/button";
import {
Calendar as CalendarIcon,
@@ -19,7 +17,6 @@ import {
RefreshCw,
Move,
Trash2,
FileText,
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { z } from "zod";
@@ -705,40 +702,6 @@ export default function AppointmentsPage() {
Edit Appointment
</span>
</Item>
<Item
onClick={({ props }) => {
const fullAppointment = appointments.find(
(a) => a.id === props.appointmentId
);
if (fullAppointment) {
// Set the appointment and patient IDs for the claim modal
setClaimAppointmentId(fullAppointment.id ?? null);
setClaimPatientId(fullAppointment.patientId);
// Find the patient name for the toast notification
const patient = patients.find(
(p) => p.id === fullAppointment.patientId
);
const patientName = patient
? `${patient.firstName} ${patient.lastName}`
: `Patient #${fullAppointment.patientId}`;
// Show a toast notification
toast({
title: "Claim Services Initiated",
description: `Started insurance claim process for ${patientName}`,
});
// Open the claim modal
setIsClaimModalOpen(true);
}
}}
>
<span className="flex items-center gap-2 text-blue-600">
<FileText className="h-4 w-4" />
Claim Services
</span>
</Item>
<Item
onClick={({ props }) =>
handleDeleteAppointment(props.appointmentId)
@@ -936,20 +899,6 @@ export default function AppointmentsPage() {
onCancel={() => setConfirmDeleteState({ open: false })}
entityName={confirmDeleteState.appointmentTitle}
/>
{/* Claim Services Modal */}
{claimPatientId && claimAppointmentId && (
<ClaimModal
open={isClaimModalOpen}
onClose={() => {
setIsClaimModalOpen(false);
setClaimPatientId(null);
setClaimAppointmentId(null);
}}
patientId={claimPatientId}
appointmentId={claimAppointmentId}
/>
)}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation} from "@tanstack/react-query";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import {
@@ -17,7 +17,6 @@ import {
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";
@@ -28,7 +27,7 @@ import {
clearTaskStatus,
} from "@/redux/slices/seleniumClaimSubmitTaskSlice";
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
import { formatLocalDate} from "@/utils/dateUtils";
import ClaimsRecentTable from "@/components/claims/claims-recent-table";
import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table";