From 26b069907d8ebf773bf493b8b591c61fb1640807 Mon Sep 17 00:00:00 2001 From: Potenz Date: Fri, 18 Jul 2025 22:33:35 +0530 Subject: [PATCH] dates utils used --- apps/Frontend/src/pages/appointments-page.tsx | 23 +- apps/Frontend/src/pages/claims-page.tsx | 49 +- apps/Frontend/src/pages/dashboard.tsx | 26 +- .../src/pages/documents-page-basic.tsx | 474 ------------------ .../src/pages/insurance-eligibility-page.tsx | 4 +- apps/Frontend/src/utils/dateUtils.ts | 66 +-- 6 files changed, 63 insertions(+), 579 deletions(-) delete mode 100644 apps/Frontend/src/pages/documents-page-basic.tsx diff --git a/apps/Frontend/src/pages/appointments-page.tsx b/apps/Frontend/src/pages/appointments-page.tsx index 5d688d9..3aa0a92 100644 --- a/apps/Frontend/src/pages/appointments-page.tsx +++ b/apps/Frontend/src/pages/appointments-page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { format, addDays, startOfToday, addMinutes } from "date-fns"; import { - parseLocalDateString, + parseLocalDate, formatLocalDate, normalizeToISOString, } from "@/utils/dateUtils"; @@ -200,7 +200,7 @@ export default function AppointmentsPage() { // 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 = new Date(selectedDate); + const startDate = parseLocalDate(selectedDate); startDate.setHours(startHour, startMinute, 0); const endDate = addMinutes(startDate, 30); @@ -377,7 +377,7 @@ export default function AppointmentsPage() { ) => { // Converts local date to exact UTC date with no offset issues - const rawDate = parseLocalDateString(appointmentData.date); + const rawDate = parseLocalDate(appointmentData.date); const updatedData = { ...appointmentData, @@ -439,12 +439,8 @@ export default function AppointmentsPage() { const formattedDate = format(selectedDate, "yyyy-MM-dd"); const selectedDateAppointments = appointments.filter((appointment) => { - const dateObj = - typeof appointment.date === "string" - ? parseLocalDateString(appointment.date) - : appointment.date; - - return formatLocalDate(dateObj) === formatLocalDate(selectedDate); // formattedDate should be 'yyyy-MM-dd' string in UTC format as well + const dateObj = parseLocalDate(appointment.date) + return formatLocalDate(dateObj) === formatLocalDate(selectedDate); }); // Process appointments for the scheduler view @@ -473,11 +469,8 @@ export default function AppointmentsPage() { ...apt, patientName, staffId, - status: apt.status ?? null, // Default to null if status is undefined - date: - apt.date instanceof Date - ? formatLocalDate(apt.date) - : formatLocalDate(new Date(apt.date)), + status: apt.status ?? null, + date: formatLocalDate(parseLocalDate(apt.date)) }; return processed; @@ -529,7 +522,7 @@ export default function AppointmentsPage() { // Calculate new end time (30 minutes from start) const startHour = parseInt(newTimeSlot.time.split(":")[0] as string); const startMinute = parseInt(newTimeSlot.time.split(":")[1] as string); - const startDate = new Date(selectedDate); + const startDate = parseLocalDate(selectedDate); startDate.setHours(startHour, startMinute, 0); const endDate = addMinutes(startDate, 30); diff --git a/apps/Frontend/src/pages/claims-page.tsx b/apps/Frontend/src/pages/claims-page.tsx index a330dc3..24cc27e 100644 --- a/apps/Frontend/src/pages/claims-page.tsx +++ b/apps/Frontend/src/pages/claims-page.tsx @@ -23,6 +23,7 @@ import { clearTaskStatus, } from "@/redux/slices/seleniumClaimSubmitTaskSlice"; import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner"; +import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; //creating types out of schema auto generated. type Appointment = z.infer; @@ -223,34 +224,11 @@ export default function ClaimsPage() { }, }); - // Converts local date to exact UTC date with no offset issues - function parseLocalDate(dateInput: Date | string): Date { - if (dateInput instanceof Date) return dateInput; - - const dateString = dateInput.split("T")[0] || dateInput; - - const parts = dateString.split("-"); - if (parts.length !== 3) { - throw new Error(`Invalid date format: ${dateString}`); - } - - const year = Number(parts[0]); - const month = Number(parts[1]); - const day = Number(parts[2]); - - if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) { - throw new Error(`Invalid date parts in date string: ${dateString}`); - } - - return new Date(year, month - 1, day); // month is 0-indexed - } - const handleAppointmentSubmit = async ( appointmentData: InsertAppointment | UpdateAppointment ): Promise => { const rawDate = parseLocalDate(appointmentData.date); - - const formattedDate = rawDate.toLocaleDateString("en-CA"); // YYYY-MM-DD format + const formattedDate = formatLocalDate(rawDate); // Prepare minimal data to update/create const minimalData = { @@ -264,7 +242,7 @@ export default function ClaimsPage() { const existingAppointment = appointments.find( (a) => a.patientId === appointmentData.patientId && - new Date(a.date).toLocaleDateString("en-CA") === formattedDate + formatLocalDate(parseLocalDate(a.date)) === formattedDate ); if (existingAppointment && typeof existingAppointment.id === "number") { @@ -374,18 +352,21 @@ export default function ClaimsPage() { const prefillClaimForm = (patient: Patient) => { const patientAppointments = appointments .filter((a) => a.patientId === patient.id) - .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + .sort( + (a, b) => + parseLocalDate(b.date).getTime() - parseLocalDate(a.date).getTime() +); const lastAppointment = patientAppointments[0]; // most recent const dateToUse = lastAppointment ? parseLocalDate(lastAppointment.date) - : new Date(); + : parseLocalDate(new Date()); setClaimFormData((prev: any) => ({ ...prev, patientId: patient.id, - serviceDate: dateToUse.toLocaleDateString("en-CA"), // consistent "YYYY-MM-DD" + serviceDate: formatLocalDate(dateToUse) })); }; @@ -405,14 +386,11 @@ export default function ClaimsPage() { const lastName = rest.join(" ") || ""; const parsedDob = parse(dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc. - const isValidDob = !isNaN(parsedDob.getTime()); - + const newPatient: InsertPatient = { firstName, lastName, - dateOfBirth: isValidDob - ? format(parsedDob, "yyyy-MM-dd") - : format(new Date(), "yyyy-MM-dd"), + dateOfBirth: formatLocalDate(parsedDob), gender: "", phone: "", userId: user?.id ?? 1, @@ -447,7 +425,8 @@ export default function ClaimsPage() { const patientAppointments = appointments .filter((appt) => appt.patientId === patient.id) .sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() + (a, b) => + parseLocalDate(b.date).getTime() - parseLocalDate(a.date).getTime() ); // Sort descending by date if (patientAppointments.length > 0) { @@ -458,7 +437,7 @@ export default function ClaimsPage() { appointmentId: latestAppointment!.id, insuranceProvider: patient.insuranceProvider || "N/A", insuranceId: patient.insuranceId || "N/A", - lastAppointment: String(latestAppointment!.date), + lastAppointment: formatLocalDate(parseLocalDate(latestAppointment!.date)), }); } diff --git a/apps/Frontend/src/pages/dashboard.tsx b/apps/Frontend/src/pages/dashboard.tsx index 2290734..77f737f 100644 --- a/apps/Frontend/src/pages/dashboard.tsx +++ b/apps/Frontend/src/pages/dashboard.tsx @@ -26,6 +26,7 @@ import { } from "lucide-react"; import { Link } from "wouter"; import { z } from "zod"; +import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; //creating types out of schema auto generated. type Appointment = z.infer; @@ -63,7 +64,6 @@ const insertPatientSchema = ( }); type InsertPatient = z.infer; - // Type for the ref to access modal methods type AddPatientModalRef = { shouldSchedule: boolean; @@ -151,9 +151,7 @@ export default function Dashboard() { } }; - const isLoading = - isLoadingPatients || - addPatientMutation.isPending; + const isLoading = isLoadingPatients || addPatientMutation.isPending; // Create appointment mutation const createAppointmentMutation = useMutation({ @@ -235,24 +233,10 @@ export default function Dashboard() { }; // Since we removed filters, just return all patients - const filteredPatients = patients; - const now = new Date(); - const todayUTC = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`; - + const today = formatLocalDate(new Date()); const todaysAppointments = appointments.filter((appointment) => { - const dateObj = - typeof appointment.date === "string" - ? parseISO(appointment.date) - : appointment.date; - - // Extract UTC year, month, day from appointment date - const year = dateObj.getUTCFullYear(); - const month = dateObj.getUTCMonth(); - const day = dateObj.getUTCDate(); - - const appointmentUTCDate = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; - - return appointmentUTCDate === todayUTC; + const parsedDate = parseLocalDate(appointment.date); + return formatLocalDate(parsedDate) === today; }); // Count completed appointments today diff --git a/apps/Frontend/src/pages/documents-page-basic.tsx b/apps/Frontend/src/pages/documents-page-basic.tsx deleted file mode 100644 index 5a81933..0000000 --- a/apps/Frontend/src/pages/documents-page-basic.tsx +++ /dev/null @@ -1,474 +0,0 @@ -import { useEffect, useState } from "react"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { TopAppBar } from "@/components/layout/top-app-bar"; -import { Sidebar } from "@/components/layout/sidebar"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { - Search, - Eye, - ChevronLeft, - ChevronRight, - Settings, - Trash, - Download, -} from "lucide-react"; -import { useAuth } from "@/hooks/use-auth"; -import { cn } from "@/lib/utils"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { ClaimPdfUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; -import { z } from "zod"; -import "@react-pdf-viewer/core/lib/styles/index.css"; -import "@react-pdf-viewer/default-layout/lib/styles/index.css"; -import { Viewer, Worker } from "@react-pdf-viewer/core"; -import { defaultLayoutPlugin } from "@react-pdf-viewer/default-layout"; -import { apiRequest, queryClient } from "@/lib/queryClient"; -import { toast } from "@/hooks/use-toast"; -import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; - -const ClaimPdfSchema = - ClaimPdfUncheckedCreateInputObjectSchema as unknown as z.ZodObject; -type ClaimPdf = z.infer; - -export default function DocumentsPage() { - const { user } = useAuth(); - const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const [searchField, setSearchField] = useState("all"); - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 5; - const [selectedPdfId, setSelectedPdfId] = useState(null); - const defaultLayoutPluginInstance = defaultLayoutPlugin(); - const [isDeletePdfOpen, setIsDeletePdfOpen] = useState(false); - const [currentPdf, setCurrentPdf] = useState(null); - - const { data: pdfs = [], isLoading } = useQuery({ - queryKey: ["/api/documents/claim-pdf/recent"], - enabled: !!user, - queryFn: async () => { - const res = await apiRequest("GET", "/api/documents/claim-pdf/recent"); - return res.json(); - }, - }); - - const deletePdfMutation = useMutation({ - mutationFn: async (id: number) => { - await apiRequest("DELETE", `/api/documents/claim-pdf/${id}`); - }, - onSuccess: () => { - setIsDeletePdfOpen(false); - setCurrentPdf(null); - queryClient.invalidateQueries({ - queryKey: ["/api/documents/claim-pdf/recent"], - }); - - toast({ - title: "Success", - description: "PDF deleted successfully!", - variant: "default", - }); - }, - onError: (error: any) => { - console.error("Error deleting PDF:", error); - toast({ - title: "Error", - description: `Failed to delete PDF: ${error.message || error}`, - variant: "destructive", - }); - }, - }); - - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }); - }; - - const getPatientInitials = (first: string, last: string) => - `${first[0]}${last[0]}`.toUpperCase(); - - const [fileBlobUrl, setFileBlobUrl] = useState(null); - - useEffect(() => { - if (!selectedPdfId) return; - let url: string | null = null; - - const fetchPdf = async () => { - try { - const res = await apiRequest( - "GET", - `/api/documents/claim-pdf/${selectedPdfId}` - ); - - const arrayBuffer = await res.arrayBuffer(); - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - const objectUrl = URL.createObjectURL(blob); - setFileBlobUrl(objectUrl); - url = objectUrl; - } catch (err) { - console.error("Failed to load PDF", err); - } - }; - - fetchPdf(); - - return () => { - if (url) { - URL.revokeObjectURL(url); - } - }; - }, [selectedPdfId]); - - const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev); - - const viewPdf = (pdfId: number) => { - setSelectedPdfId(pdfId); - }; - - const downloadPdf = async (pdfId: number, filename: string) => { - try { - const res = await apiRequest("GET", `/api/documents/claim-pdf/${pdfId}`); - const arrayBuffer = await res.arrayBuffer(); - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - const url = URL.createObjectURL(blob); - - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (err) { - console.error("Failed to download PDF:", err); - } - }; - - const handleDeletePdf = (pdf: ClaimPdf) => { - setCurrentPdf(pdf); - setIsDeletePdfOpen(true); - }; - - const handleConfirmDeletePdf = () => { - if (currentPdf) { - deletePdfMutation.mutate(currentPdf.id); - } else { - toast({ - title: "Error", - description: "No PDF selected for deletion.", - variant: "destructive", - }); - } - }; - - const filteredPdfs = pdfs.filter((pdf) => { - const patient = pdf.patient; - const searchLower = searchTerm.toLowerCase(); - const fullName = `${patient.firstName} ${patient.lastName}`.toLowerCase(); - const patientId = `PID-${patient.id.toString().padStart(4, "0")}`; - - switch (searchField) { - case "name": - return fullName.includes(searchLower); - case "id": - return patientId.toLowerCase().includes(searchLower); - case "phone": - return patient.phone?.toLowerCase().includes(searchLower) || false; - case "all": - default: - return ( - fullName.includes(searchLower) || - patientId.includes(searchLower) || - patient.phone?.toLowerCase().includes(searchLower) || - patient.email?.toLowerCase().includes(searchLower) || - false - ); - } - }); - - const totalPages = Math.ceil(filteredPdfs.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const currentPdfs = filteredPdfs.slice(startIndex, startIndex + itemsPerPage); - - return ( -
- - -
- - -
-
-
-

- Documents -

-

- View and manage recent uploaded claim PDFs -

-
- - - -
-
- - setSearchTerm(e.target.value)} - className="pl-10" - /> -
-
- - -
-
-
-
- - - - {isLoading ? ( -
Loading data...
- ) : currentPdfs.length === 0 ? ( -
- {searchTerm - ? "No results matching your search." - : "No recent claim PDFs available."} -
- ) : ( - <> -
-
Patient
-
DOB / Gender
-
Contact
-
Insurance
-
Status
-
Actions
-
- - {currentPdfs.map((pdf) => { - const patient = pdf.patient; - return ( -
-
-
- {getPatientInitials( - patient.firstName, - patient.lastName - )} -
-
-
- {patient.firstName} {patient.lastName} -
-
- PID-{patient.id.toString().padStart(4, "0")} -
-
-
- -
-
- {formatDate(patient.dateOfBirth)} -
-
- {patient.gender} -
-
- -
-
- {patient.phone || "Not provided"} -
-
- {patient.email || "No email"} -
-
- -
-
- {patient.insuranceProvider - ? `${patient.insuranceProvider.charAt(0).toUpperCase()}${patient.insuranceProvider.slice(1)}` - : "Not specified"} -
-
- ID: {patient.insuranceId || "N/A"} -
-
- -
- - {patient.status === "active" - ? "Active" - : "Inactive"} - -
- -
-
- - - - -
-
-
- ); - })} - - setIsDeletePdfOpen(false)} - entityName={`PDF #${currentPdf?.id}`} - /> - - {/* PDF Viewer */} - {selectedPdfId && fileBlobUrl && ( -
-
-

- Viewing PDF #{selectedPdfId} -

- -
-
- - - -
-
- )} - - {/* Pagination */} - {totalPages > 1 && ( -
-
- Showing {startIndex + 1} to{" "} - {Math.min( - startIndex + itemsPerPage, - filteredPdfs.length - )}{" "} - of {filteredPdfs.length} results -
-
- - {Array.from( - { length: totalPages }, - (_, i) => i + 1 - ).map((page) => ( - - ))} - -
-
- )} - - )} -
-
-
-
-
-
- ); -} diff --git a/apps/Frontend/src/pages/insurance-eligibility-page.tsx b/apps/Frontend/src/pages/insurance-eligibility-page.tsx index c507d9c..3f09289 100644 --- a/apps/Frontend/src/pages/insurance-eligibility-page.tsx +++ b/apps/Frontend/src/pages/insurance-eligibility-page.tsx @@ -33,7 +33,7 @@ import { clearTaskStatus, } from "@/redux/slices/seleniumEligibilityCheckTaskSlice"; import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner"; -import { formatLocalDate, parseLocalDateString } from "@/utils/dateUtils"; +import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils"; const PatientSchema = ( PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject @@ -85,7 +85,7 @@ export default function InsuranceEligibilityPage() { const dob = typeof selectedPatient.dateOfBirth === "string" - ? parseLocalDateString(selectedPatient.dateOfBirth) + ? parseLocalDate(selectedPatient.dateOfBirth) : selectedPatient.dateOfBirth; setDateOfBirth(dob); } else { diff --git a/apps/Frontend/src/utils/dateUtils.ts b/apps/Frontend/src/utils/dateUtils.ts index e455491..32c1a45 100644 --- a/apps/Frontend/src/utils/dateUtils.ts +++ b/apps/Frontend/src/utils/dateUtils.ts @@ -2,39 +2,47 @@ * Parse a date string in yyyy-MM-dd format (assumed local) into a JS Date object. * No timezone conversion is applied. Returns a Date at midnight local time. */ -export function parseLocalDateString(dateStr: string): Date { - const parts = dateStr.split("-"); - // Destructure with fallback - const [yearStr, monthStr, dayStr] = parts; - - // Validate all parts are defined and valid strings - if (!yearStr || !monthStr || !dayStr) { - throw new Error("Invalid date string format. Expected yyyy-MM-dd."); +export function parseLocalDate(input: string | Date): Date { + if (input instanceof Date) { + return new Date(input.getFullYear(), input.getMonth(), input.getDate()); } - const year = parseInt(yearStr, 10); - const month = parseInt(monthStr, 10) - 1; // JS Date months are 0-based - const day = parseInt(dayStr, 10); + if (typeof input === "string") { + const dateString = input?.split("T")[0] ?? ""; + const parts = dateString.split("-"); - if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) { - throw new Error("Invalid numeric values in date string."); + const [yearStr, monthStr, dayStr] = parts; + + // Validate all parts are defined and valid strings + if (!yearStr || !monthStr || !dayStr) { + throw new Error("Invalid date string format. Expected yyyy-MM-dd."); + } + + const year = parseInt(yearStr, 10); + const month = parseInt(monthStr, 10) - 1; // JS Date months are 0-based + const day = parseInt(dayStr, 10); + + if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) { + throw new Error("Invalid numeric values in date string."); + } + + return new Date(year, month, day); } - - return new Date(year, month, day); + throw new Error( + "Unsupported input to parseLocalDate. Expected string or Date." + ); } - - /** * Format a JS Date object as a `yyyy-MM-dd` string (in local time). * Useful for saving date-only data without time component. */ export function formatLocalDate(date: Date): string { - const year = date.getFullYear(); // ← local time + const year = date.getFullYear(); // ← local time const month = `${date.getMonth() + 1}`.padStart(2, "0"); const day = `${date.getDate()}`.padStart(2, "0"); - return `${year}-${month}-${day}`; // e.g. "2025-07-15" + return `${year}-${month}-${day}`; // e.g. "2025-07-15" } /** @@ -42,7 +50,9 @@ export function formatLocalDate(date: Date): string { * Useful for comparing or storing dates consistently across timezones. */ export function toUTCDate(date: Date): Date { - return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + return new Date( + Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) + ); } /** @@ -50,23 +60,15 @@ export function toUTCDate(date: Date): Date { * and formats it as local yyyy-MM-dd string for UI use. */ export function formatUTCDateStringToLocal(dateStr: string): string { - const date = new Date(dateStr); // still UTC - - // Create a local Date object with same year, month, day from UTC - const localDate = new Date( - date.getUTCFullYear(), - date.getUTCMonth(), - date.getUTCDate() - ); - - return formatLocalDate(localDate); // now safely in local time + const localDate = parseLocalDate(dateStr); // will strip the time part + return formatLocalDate(localDate); // e.g., "2025-07-15" } - /** * Ensure any date (Date|string) is formatted to ISO string for consistent backend storage. * If it's already a string, pass through. If it's a Date, convert to ISO. */ export function normalizeToISOString(date: Date | string): string { - return date instanceof Date ? date.toISOString() : date; + const parsed = parseLocalDate(date); + return parsed.toISOString(); // ensures it always starts from local midnight }