claim routes connection done
This commit is contained in:
@@ -2,9 +2,7 @@ import { Router } from "express";
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { storage } from "../storage";
|
import { storage } from "../storage";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
ClaimUncheckedCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -31,6 +29,13 @@ const updateClaimSchema = (
|
|||||||
|
|
||||||
type UpdateClaim = z.infer<typeof updateClaimSchema>;
|
type UpdateClaim = z.infer<typeof updateClaimSchema>;
|
||||||
|
|
||||||
|
// Extend the schema to inject `userId` manually (since it's not passed by the client)
|
||||||
|
const ExtendedClaimSchema = (
|
||||||
|
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).extend({
|
||||||
|
userId: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
|
|
||||||
// Get all claims for the logged-in user
|
// Get all claims for the logged-in user
|
||||||
@@ -48,11 +53,11 @@ router.get("/:id", async (req: Request, res: Response): Promise<any> => {
|
|||||||
try {
|
try {
|
||||||
const idParam = req.params.id;
|
const idParam = req.params.id;
|
||||||
if (!idParam) {
|
if (!idParam) {
|
||||||
return res.status(400).json({ error: "Missing claim ID" });
|
return res.status(400).json({ error: "Missing claim ID" });
|
||||||
}
|
}
|
||||||
const claimId = parseInt(idParam, 10);
|
const claimId = parseInt(idParam, 10);
|
||||||
if (isNaN(claimId)) {
|
if (isNaN(claimId)) {
|
||||||
return res.status(400).json({ error: "Invalid claim ID" });
|
return res.status(400).json({ error: "Invalid claim ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const claim = await storage.getClaim(claimId);
|
const claim = await storage.getClaim(claimId);
|
||||||
@@ -73,12 +78,16 @@ router.get("/:id", async (req: Request, res: Response): Promise<any> => {
|
|||||||
// Create a new claim
|
// Create a new claim
|
||||||
router.post("/", async (req: Request, res: Response): Promise<any> => {
|
router.post("/", async (req: Request, res: Response): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const claimData = ClaimSchema.parse({
|
if (Array.isArray(req.body.serviceLines)) {
|
||||||
|
req.body.serviceLines = { create: req.body.serviceLines };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedClaim = ExtendedClaimSchema.parse({
|
||||||
...req.body,
|
...req.body,
|
||||||
userId: req.user!.id,
|
userId: req.user!.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newClaim = await storage.createClaim(claimData);
|
const newClaim = await storage.createClaim(parsedClaim);
|
||||||
res.status(201).json(newClaim);
|
res.status(201).json(newClaim);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
@@ -87,7 +96,14 @@ router.post("/", async (req: Request, res: Response): Promise<any> => {
|
|||||||
errors: error.format(),
|
errors: error.format(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.status(500).json({ message: "Failed to create claim" });
|
|
||||||
|
console.error("❌ Failed to create claim:", error); // logs full error to server
|
||||||
|
|
||||||
|
// Send more detailed info to the client (for dev only)
|
||||||
|
return res.status(500).json({
|
||||||
|
message: "Failed to create claim",
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,12 +112,12 @@ router.put("/:id", async (req: Request, res: Response): Promise<any> => {
|
|||||||
try {
|
try {
|
||||||
const idParam = req.params.id;
|
const idParam = req.params.id;
|
||||||
if (!idParam) {
|
if (!idParam) {
|
||||||
return res.status(400).json({ error: "Missing claim ID" });
|
return res.status(400).json({ error: "Missing claim ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimId = parseInt(idParam, 10);
|
const claimId = parseInt(idParam, 10);
|
||||||
if (isNaN(claimId)) {
|
if (isNaN(claimId)) {
|
||||||
return res.status(400).json({ error: "Invalid claim ID" });
|
return res.status(400).json({ error: "Invalid claim ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingClaim = await storage.getClaim(claimId);
|
const existingClaim = await storage.getClaim(claimId);
|
||||||
@@ -132,12 +148,12 @@ router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
|
|||||||
try {
|
try {
|
||||||
const idParam = req.params.id;
|
const idParam = req.params.id;
|
||||||
if (!idParam) {
|
if (!idParam) {
|
||||||
return res.status(400).json({ error: "Missing claim ID" });
|
return res.status(400).json({ error: "Missing claim ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimId = parseInt(idParam, 10);
|
const claimId = parseInt(idParam, 10);
|
||||||
if (isNaN(claimId)) {
|
if (isNaN(claimId)) {
|
||||||
return res.status(400).json({ error: "Invalid claim ID" });
|
return res.status(400).json({ error: "Invalid claim ID" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingClaim = await storage.getClaim(claimId);
|
const existingClaim = await storage.getClaim(claimId);
|
||||||
|
|||||||
@@ -255,19 +255,18 @@ export function ClaimForm({
|
|||||||
}, [patient]);
|
}, [patient]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setForm((prevForm) => {
|
setForm((prevForm) => {
|
||||||
const updatedLines = prevForm.serviceLines.map((line) => ({
|
const updatedLines = prevForm.serviceLines.map((line) => ({
|
||||||
...line,
|
...line,
|
||||||
procedureDate: serviceDate, // set all to current serviceDate string
|
procedureDate: serviceDate, // set all to current serviceDate string
|
||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
...prevForm,
|
...prevForm,
|
||||||
serviceLines: updatedLines,
|
serviceLines: updatedLines,
|
||||||
serviceDate, // keep form.serviceDate in sync as well
|
serviceDate, // keep form.serviceDate in sync as well
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [serviceDate]);
|
}, [serviceDate]);
|
||||||
|
|
||||||
|
|
||||||
// Handle patient field changes (to make inputs controlled and editable)
|
// Handle patient field changes (to make inputs controlled and editable)
|
||||||
const updatePatientField = (field: keyof Patient, value: any) => {
|
const updatePatientField = (field: keyof Patient, value: any) => {
|
||||||
@@ -293,18 +292,17 @@ export function ClaimForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateProcedureDate = (index: number, date: Date | undefined) => {
|
const updateProcedureDate = (index: number, date: Date | undefined) => {
|
||||||
if (!date) return;
|
if (!date) return;
|
||||||
|
|
||||||
const formattedDate = format(date, "MM/dd/yyyy");
|
const formattedDate = format(date, "MM/dd/yyyy");
|
||||||
const updatedLines = [...form.serviceLines];
|
const updatedLines = [...form.serviceLines];
|
||||||
|
|
||||||
if (updatedLines[index]) {
|
if (updatedLines[index]) {
|
||||||
updatedLines[index].procedureDate = formattedDate;
|
updatedLines[index].procedureDate = formattedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
setForm({ ...form, serviceLines: updatedLines });
|
|
||||||
};
|
|
||||||
|
|
||||||
|
setForm({ ...form, serviceLines: updatedLines });
|
||||||
|
};
|
||||||
|
|
||||||
// FILE UPLOAD ZONE
|
// FILE UPLOAD ZONE
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
||||||
@@ -335,7 +333,7 @@ export function ClaimForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Delta MA Button Handler
|
// Delta MA Button Handler
|
||||||
const handleDeltaMASubmit = () => {
|
const handleDeltaMASubmit = async () => {
|
||||||
const appointmentData = {
|
const appointmentData = {
|
||||||
patientId: patientId,
|
patientId: patientId,
|
||||||
date: convertToISODate(serviceDate),
|
date: convertToISODate(serviceDate),
|
||||||
@@ -343,7 +341,7 @@ export function ClaimForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 1. Create or update appointment
|
// 1. Create or update appointment
|
||||||
onHandleAppointmentSubmit(appointmentData);
|
const appointmentId = await onHandleAppointmentSubmit(appointmentData);
|
||||||
|
|
||||||
// 2. Update patient
|
// 2. Update patient
|
||||||
if (patient && typeof patient.id === "number") {
|
if (patient && typeof patient.id === "number") {
|
||||||
@@ -368,6 +366,7 @@ export function ClaimForm({
|
|||||||
staffId: Number(staff?.id),
|
staffId: Number(staff?.id),
|
||||||
patientId: patient?.id,
|
patientId: patient?.id,
|
||||||
insuranceProvider: "Delta MA",
|
insuranceProvider: "Delta MA",
|
||||||
|
appointmentId: appointmentId!,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. Close form
|
// 4. Close form
|
||||||
|
|||||||
@@ -124,34 +124,34 @@ export default function ClaimsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update patient mutation
|
// Update patient mutation
|
||||||
const updatePatientMutation = useMutation({
|
const updatePatientMutation = useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
id,
|
id,
|
||||||
patient,
|
patient,
|
||||||
}: {
|
}: {
|
||||||
id: number;
|
id: number;
|
||||||
patient: UpdatePatient;
|
patient: UpdatePatient;
|
||||||
}) => {
|
}) => {
|
||||||
const res = await apiRequest("PUT", `/api/patients/${id}`, patient);
|
const res = await apiRequest("PUT", `/api/patients/${id}`, patient);
|
||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: "Patient updated successfully!",
|
description: "Patient updated successfully!",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: `Failed to update patient: ${error.message}`,
|
description: `Failed to update patient: ${error.message}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create appointment mutation
|
// Create appointment mutation
|
||||||
const createAppointmentMutation = useMutation({
|
const createAppointmentMutation = useMutation({
|
||||||
mutationFn: async (appointment: InsertAppointment) => {
|
mutationFn: async (appointment: InsertAppointment) => {
|
||||||
@@ -177,41 +177,41 @@ export default function ClaimsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update appointment mutation
|
// Update appointment mutation
|
||||||
const updateAppointmentMutation = useMutation({
|
const updateAppointmentMutation = useMutation({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
id,
|
id,
|
||||||
appointment,
|
appointment,
|
||||||
}: {
|
}: {
|
||||||
id: number;
|
id: number;
|
||||||
appointment: UpdateAppointment;
|
appointment: UpdateAppointment;
|
||||||
}) => {
|
}) => {
|
||||||
const res = await apiRequest(
|
const res = await apiRequest(
|
||||||
"PUT",
|
"PUT",
|
||||||
`/api/appointments/${id}`,
|
`/api/appointments/${id}`,
|
||||||
appointment
|
appointment
|
||||||
);
|
);
|
||||||
return await res.json();
|
return await res.json();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({
|
toast({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
description: "Appointment updated successfully.",
|
description: "Appointment updated successfully.",
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
|
queryClient.invalidateQueries({ queryKey: ["/api/appointments/all"] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
queryClient.invalidateQueries({ queryKey: ["/api/patients/"] });
|
||||||
},
|
},
|
||||||
onError: (error: Error) => {
|
onError: (error: Error) => {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: `Failed to update appointment: ${error.message}`,
|
description: `Failed to update appointment: ${error.message}`,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAppointmentSubmit = (
|
const handleAppointmentSubmit = async (
|
||||||
appointmentData: InsertAppointment | UpdateAppointment
|
appointmentData: InsertAppointment | UpdateAppointment
|
||||||
) => {
|
): Promise<number> => {
|
||||||
// Converts local date to exact UTC date with no offset issues
|
// Converts local date to exact UTC date with no offset issues
|
||||||
function parseLocalDate(dateInput: Date | string): Date {
|
function parseLocalDate(dateInput: Date | string): Date {
|
||||||
if (dateInput instanceof Date) return dateInput;
|
if (dateInput instanceof Date) return dateInput;
|
||||||
@@ -250,23 +250,39 @@ export default function ClaimsPage() {
|
|||||||
new Date(a.date).toLocaleDateString("en-CA") === formattedDate
|
new Date(a.date).toLocaleDateString("en-CA") === formattedDate
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingAppointment && typeof existingAppointment.id === 'number') {
|
if (existingAppointment && typeof existingAppointment.id === "number") {
|
||||||
// Update appointment with only date
|
// Update appointment with only date
|
||||||
updateAppointmentMutation.mutate({
|
updateAppointmentMutation.mutate({
|
||||||
id: existingAppointment.id,
|
id: existingAppointment.id,
|
||||||
appointment: minimalData,
|
appointment: minimalData,
|
||||||
});
|
});
|
||||||
} else {
|
return existingAppointment.id;
|
||||||
// Create new appointment with required fields + defaults
|
|
||||||
createAppointmentMutation.mutate({
|
|
||||||
...minimalData,
|
|
||||||
patientId: appointmentData.patientId,
|
|
||||||
userId:user?.id,
|
|
||||||
title: "Scheduled Appointment", // default title
|
|
||||||
type: "checkup", // default type
|
|
||||||
} as InsertAppointment);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createClaimMutation = useMutation({
|
const createClaimMutation = useMutation({
|
||||||
@@ -322,6 +338,15 @@ export default function ClaimsPage() {
|
|||||||
patientId: null,
|
patientId: null,
|
||||||
serviceDate: "",
|
serviceDate: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove query parameters without reload
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete("memberId");
|
||||||
|
url.searchParams.delete("dob");
|
||||||
|
url.searchParams.delete("name");
|
||||||
|
|
||||||
|
// Use history.replaceState to update the URL without reloading
|
||||||
|
window.history.replaceState({}, document.title, url.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefillClaimForm = (patient: Patient) => {
|
const prefillClaimForm = (patient: Patient) => {
|
||||||
@@ -376,7 +401,7 @@ export default function ClaimsPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [memberId, dob, patients]);
|
}, [memberId, dob]);
|
||||||
|
|
||||||
function handleClaimSubmit(claimData: any) {
|
function handleClaimSubmit(claimData: any) {
|
||||||
createClaimMutation.mutate(claimData);
|
createClaimMutation.mutate(claimData);
|
||||||
@@ -467,9 +492,7 @@ export default function ClaimsPage() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (patientsWithAppointments.length > 0) {
|
if (patientsWithAppointments.length > 0) {
|
||||||
const firstPatient = patientsWithAppointments[0];
|
const firstPatient = patientsWithAppointments[0];
|
||||||
handleNewClaim(
|
handleNewClaim(Number(firstPatient?.patientId));
|
||||||
Number(firstPatient?.patientId),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: "No patients available",
|
title: "No patients available",
|
||||||
@@ -503,9 +526,7 @@ export default function ClaimsPage() {
|
|||||||
<div
|
<div
|
||||||
key={item.patientId}
|
key={item.patientId}
|
||||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||||
onClick={() =>
|
onClick={() => handleNewClaim(item.patientId)}
|
||||||
handleNewClaim(item.patientId)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">{item.patientName}</h3>
|
<h3 className="font-medium">{item.patientName}</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user