types updated

This commit is contained in:
2025-08-10 18:21:38 +05:30
parent 31ed4cd1da
commit 4b1ee273e4
44 changed files with 2049 additions and 1263 deletions

View File

@@ -1,33 +1,22 @@
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { AppointmentForm } from "./appointment-form";
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import {z} from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
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>;
import {
Appointment,
InsertAppointment,
Patient,
UpdateAppointment,
} from "@repo/db/types";
interface AddAppointmentModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
onDelete?: (id: number) => void;
onDelete?: (id: number) => void;
isLoading: boolean;
appointment?: Appointment;
patients: Patient[];
@@ -42,7 +31,6 @@ export function AddAppointmentModal({
appointment,
patients,
}: AddAppointmentModalProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md">
@@ -61,10 +49,10 @@ export function AddAppointmentModal({
}}
isLoading={isLoading}
onDelete={onDelete}
onOpenChange={onOpenChange}
onOpenChange={onOpenChange}
/>
</div>
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
@@ -33,41 +33,13 @@ import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { useDebounce } from "use-debounce";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
const insertAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
userId: true,
});
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
const updateAppointmentSchema = (
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: 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>;
Appointment,
InsertAppointment,
insertAppointmentSchema,
Patient,
Staff,
UpdateAppointment,
} from "@repo/db/types";
interface AppointmentFormProps {
appointment?: Appointment;
@@ -211,7 +183,7 @@ export function AppointmentForm({
const term = debouncedSearchTerm.toLowerCase();
setFilteredPatients(
patients.filter((p) =>
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`
`${p.firstName} ${p.lastName} ${p.phone} ${p.dateOfBirth}`
.toLowerCase()
.includes(term)
)
@@ -351,7 +323,7 @@ export function AppointmentForm({
filteredPatients.map((patient) => (
<SelectItem
key={patient.id}
value={patient.id.toString()}
value={patient.id?.toString() ?? ""}
>
<div className="flex flex-col">
<span className="font-medium">
@@ -435,7 +407,7 @@ export function AppointmentForm({
selected={field.value}
onSelect={(date) => {
if (date) {
field.onChange(date);
field.onChange(date);
}
}}
disabled={(date: Date) =>

View File

@@ -25,20 +25,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod";
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
import { Appointment, Patient } from "@repo/db/types";
interface AppointmentTableProps {
appointments: Appointment[];

View File

@@ -15,29 +15,7 @@ import {
} from "@/components/ui/select";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
import React, { useState } from "react";
import { z } from "zod";
import {
ClaimUncheckedCreateInputObjectSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { ClaimStatus } from "./claims-recent-table";
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
type ClaimWithServiceLines = Claim & {
serviceLines: {
id: number;
claimId: number;
procedureCode: string;
procedureDate: Date;
oralCavityArea: string | null;
toothNumber: string | null;
toothSurface: string | null;
billedAmount: number;
}[];
staff: Staff | null;
};
import { ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
type ClaimEditModalProps = {
isOpen: boolean;
@@ -223,14 +201,17 @@ export default function ClaimEditModal({
)}
<p>
<span className="text-gray-500">Billed Amount:</span> $
{line.billedAmount.toFixed(2)}
{line.totalBilled.toFixed(2)}
</p>
</div>
))}
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
Total Billed Amount: $
{claim.serviceLines
.reduce((total, line) => total + line.billedAmount, 0)
.reduce(
(total, line) => total + line.totalBilled?.toNumber(),
0
)
.toFixed(2)}
</div>
</>

View File

@@ -18,14 +18,6 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
PatientUncheckedCreateInputObjectSchema,
AppointmentUncheckedCreateInputObjectSchema,
ClaimUncheckedCreateInputObjectSchema,
ClaimStatusSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod";
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@/lib/queryClient";
import { MultipleFileUploadZone } from "../file-upload/multiple-file-upload-zone";
@@ -37,57 +29,16 @@ import {
} from "@/components/ui/tooltip";
import procedureCodes from "../../assets/data/procedureCodes.json";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
//creating types out of schema auto generated.
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,
userId: true,
})
.partial();
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
interface ServiceLine {
procedureCode: string;
procedureDate: string; // YYYY-MM-DD
oralCavityArea?: string;
toothNumber?: string;
toothSurface?: string;
billedAmount: number;
}
import {
Claim,
InputServiceLine,
InsertAppointment,
Patient,
Staff,
UpdateAppointment,
UpdatePatient,
} from "@repo/db/types";
import { Decimal } from "decimal.js";
interface ClaimFormData {
patientId: number;
@@ -102,7 +53,7 @@ interface ClaimFormData {
insuranceProvider: string;
insuranceSiteKey?: string;
status: string; // default "pending"
serviceLines: ServiceLine[];
serviceLines: InputServiceLine[];
claimId?: number;
}
@@ -117,9 +68,6 @@ interface ClaimFormProps {
onClose: () => void;
}
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
export function ClaimForm({
patientId,
onHandleAppointmentSubmit,
@@ -207,16 +155,21 @@ export function ClaimForm({
}, [serviceDate]);
// Determine patient date of birth format - required as date extracted from pdfs has different format.
const formatDOB = (dob: string | undefined) => {
const formatDOB = (dob: string | Date | undefined) => {
if (!dob) return "";
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dob)) return dob; // already MM/DD/YYYY
if (/^\d{4}-\d{2}-\d{2}/.test(dob)) {
const datePart = dob?.split("T")[0]; // safe optional chaining
if (!datePart) return "";
const [year, month, day] = datePart.split("-");
const normalized = formatLocalDate(parseLocalDate(dob));
// If it's already MM/DD/YYYY, leave it alone
if (/^\d{2}\/\d{2}\/\d{4}$/.test(normalized)) return normalized;
// If it's yyyy-MM-dd, swap order to MM/DD/YYYY
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
const [year, month, day] = normalized.split("-");
return `${month}/${day}/${year}`;
}
return dob;
return normalized;
};
// MAIN FORM INITIAL STATE
@@ -226,7 +179,7 @@ export function ClaimForm({
userId: Number(user?.id),
staffId: Number(staff?.id),
patientName: `${patient?.firstName} ${patient?.lastName}`.trim(),
memberId: patient?.insuranceId,
memberId: patient?.insuranceId ?? "",
dateOfBirth: formatDOB(patient?.dateOfBirth),
remarks: "",
serviceDate: serviceDate,
@@ -239,7 +192,9 @@ export function ClaimForm({
oralCavityArea: "",
toothNumber: "",
toothSurface: "",
billedAmount: 0,
totalBilled: new Decimal(0),
totalAdjusted: new Decimal(0),
totalPaid: new Decimal(0),
})),
uploadedFiles: [],
});
@@ -251,10 +206,10 @@ export function ClaimForm({
`${patient.firstName || ""} ${patient.lastName || ""}`.trim();
setForm((prev) => ({
...prev,
patientId: Number(patient.id),
patientName: fullName,
dateOfBirth: formatDOB(patient.dateOfBirth),
memberId: patient.insuranceId || "",
patientId: patient.id,
}));
}
}, [patient]);
@@ -266,16 +221,16 @@ export function ClaimForm({
const updateServiceLine = (
index: number,
field: keyof ServiceLine,
field: keyof InputServiceLine,
value: any
) => {
const updatedLines = [...form.serviceLines];
if (updatedLines[index]) {
if (field === "billedAmount") {
if (field === "totalBilled") {
const num = typeof value === "string" ? parseFloat(value) : value;
const rounded = Math.round((isNaN(num) ? 0 : num) * 100) / 100;
updatedLines[index][field] = rounded;
updatedLines[index][field] = new Decimal(rounded);
} else {
updatedLines[index][field] = value;
}
@@ -672,16 +627,20 @@ export function ClaimForm({
type="number"
step="0.01"
placeholder="$0.00"
value={line.billedAmount === 0 ? "" : line.billedAmount}
value={
line.totalBilled?.toNumber() === 0
? ""
: line.totalBilled?.toNumber()
}
onChange={(e) => {
updateServiceLine(i, "billedAmount", e.target.value);
updateServiceLine(i, "totalBilled", e.target.value);
}}
onBlur={(e) => {
const val = parseFloat(e.target.value);
const rounded = Math.round(val * 100) / 100;
updateServiceLine(
i,
"billedAmount",
"totalBilled",
isNaN(rounded) ? 0 : rounded
);
}}
@@ -702,7 +661,9 @@ export function ClaimForm({
oralCavityArea: "",
toothNumber: "",
toothSurface: "",
billedAmount: 0,
totalBilled: new Decimal(0),
totalAdjusted: new Decimal(0),
totalPaid: new Decimal(0),
},
],
}))

View File

@@ -6,31 +6,9 @@ import {
DialogDescription,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
ClaimUncheckedCreateInputObjectSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import React from "react";
import { z } from "zod";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
//creating types out of schema auto generated.
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
type ClaimWithServiceLines = Claim & {
serviceLines: {
id: number;
claimId: number;
procedureCode: string;
procedureDate: Date;
oralCavityArea: string | null;
toothNumber: string | null;
toothSurface: string | null;
billedAmount: number;
}[];
staff: Staff | null;
};
import { ClaimWithServiceLines } from "@repo/db/types";
type ClaimViewModalProps = {
isOpen: boolean;
@@ -205,14 +183,17 @@ export default function ClaimViewModal({
)}
<p>
<span className="text-gray-500">Billed Amount:</span>{" "}
${line.billedAmount.toFixed(2)}
${line.totalBilled.toFixed(2)}
</p>
</div>
))}
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
Total Billed Amount: $
{claim.serviceLines
.reduce((total, line) => total + line.billedAmount, 0)
.reduce(
(total, line) => total + line.totalBilled?.toNumber(),
0
)
.toFixed(2)}
</div>
</>

View File

@@ -1,8 +1,6 @@
import { useState } from "react";
import ClaimsRecentTable from "./claims-recent-table";
import { PatientTable } from "../patients/patient-table";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { z } from "zod";
import {
Card,
CardContent,
@@ -10,19 +8,15 @@ import {
CardHeader,
CardTitle,
} from "../ui/card";
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
import { Patient } from "@repo/db/types";
interface ClaimsOfPatientModalProps {
onNewClaim?: (patientId: number) => void;
}
export default function ClaimsOfPatientModal({ onNewClaim }: ClaimsOfPatientModalProps) {
export default function ClaimsOfPatientModal({
onNewClaim,
}: ClaimsOfPatientModalProps) {
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [claimsPage, setClaimsPage] = useState(1);

View File

@@ -28,45 +28,13 @@ import {
PaginationPrevious,
} from "@/components/ui/pagination";
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
import {
PatientUncheckedCreateInputObjectSchema,
ClaimUncheckedCreateInputObjectSchema,
ClaimStatusSchema,
StaffUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod";
import LoadingScreen from "@/components/ui/LoadingScreen";
import { Checkbox } from "@/components/ui/checkbox";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
import ClaimViewModal from "./claim-view-modal";
import ClaimEditModal from "./claim-edit-modal";
//creating types out of schema auto generated.
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
type ClaimWithServiceLines = Claim & {
serviceLines: {
id: number;
claimId: number;
procedureCode: string;
procedureDate: Date;
oralCavityArea: string | null;
toothNumber: string | null;
toothSurface: string | null;
billedAmount: number;
}[];
staff: Staff | null;
};
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
import { Claim, ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
interface ClaimApiResponse {
claims: ClaimWithServiceLines[];
@@ -314,7 +282,7 @@ export default function ClaimsRecentTable({
const getTotalBilled = (claim: ClaimWithServiceLines) => {
return claim.serviceLines.reduce(
(sum, line) => sum + (line.billedAmount || 0),
(sum, line) => sum + (line.totalBilled?.toNumber() || 0),
0
);
};

View File

@@ -1,8 +1,8 @@
import React, { useState, useRef, useCallback } from 'react';
import { Upload, File, X, FilePlus } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useToast } from '@/hooks/use-toast';
import { cn } from '@/lib/utils';
import React, { useState, useRef, useCallback } from "react";
import { Upload, File, X, FilePlus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { cn } from "@/lib/utils";
interface FileUploadZoneProps {
onFileUpload: (file: File) => void;
@@ -10,10 +10,10 @@ interface FileUploadZoneProps {
acceptedFileTypes?: string;
}
export function FileUploadZone({
onFileUpload,
isUploading,
acceptedFileTypes = "application/pdf"
export function FileUploadZone({
onFileUpload,
isUploading,
acceptedFileTypes = "application/pdf",
}: FileUploadZoneProps) {
const { toast } = useToast();
const [isDragging, setIsDragging] = useState(false);
@@ -32,13 +32,16 @@ export function FileUploadZone({
setIsDragging(false);
}, []);
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (!isDragging) {
setIsDragging(true);
}
}, [isDragging]);
const handleDragOver = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
if (!isDragging) {
setIsDragging(true);
}
},
[isDragging]
);
const validateFile = (file: File) => {
// Check file type
@@ -46,49 +49,55 @@ export function FileUploadZone({
toast({
title: "Invalid file type",
description: "Please upload a PDF file.",
variant: "destructive"
variant: "destructive",
});
return false;
}
// Check file size (limit to 5MB)
if (file.size > 5 * 1024 * 1024) {
toast({
title: "File too large",
description: "File size should be less than 5MB.",
variant: "destructive"
variant: "destructive",
});
return false;
}
return true;
};
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const file = e.dataTransfer.files[0];
if (validateFile(file)) {
setUploadedFile(file);
onFileUpload(file);
}
}
}, [onFileUpload, acceptedFileTypes, toast]);
const handleDrop = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
if (validateFile(file)) {
setUploadedFile(file);
onFileUpload(file);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
const file = e.dataTransfer.files[0];
if (validateFile(file)) {
setUploadedFile(file);
onFileUpload(file);
}
}
}
}, [onFileUpload, acceptedFileTypes, toast]);
},
[onFileUpload, acceptedFileTypes, toast]
);
const handleFileSelect = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
if (validateFile(file)) {
setUploadedFile(file);
onFileUpload(file);
}
}
},
[onFileUpload, acceptedFileTypes, toast]
);
const handleBrowseClick = () => {
if (fileInputRef.current) {
@@ -109,11 +118,13 @@ export function FileUploadZone({
onChange={handleFileSelect}
accept={acceptedFileTypes}
/>
<div
className={cn(
"border-2 border-dashed rounded-lg p-8 flex flex-col items-center justify-center text-center transition-colors",
isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25",
isDragging
? "border-primary bg-primary/5"
: "border-muted-foreground/25",
uploadedFile ? "bg-success/5" : "hover:bg-muted/40",
isUploading && "opacity-50 cursor-not-allowed"
)}
@@ -135,7 +146,7 @@ export function FileUploadZone({
<div className="flex flex-col items-center gap-4">
<div className="relative">
<File className="h-12 w-12 text-primary" />
<button
<button
className="absolute -top-2 -right-2 bg-background rounded-full p-1 shadow-sm border"
onClick={(e) => {
e.stopPropagation();
@@ -166,9 +177,9 @@ export function FileUploadZone({
Or click to browse files
</p>
</div>
<Button
type="button"
variant="secondary"
<Button
type="button"
variant="secondary"
onClick={(e) => {
e.stopPropagation();
handleBrowseClick();
@@ -184,4 +195,4 @@ export function FileUploadZone({
</div>
</div>
);
}
}

View File

@@ -20,12 +20,12 @@ interface CredentialsModalProps {
isLoading?: boolean;
}
export function CredentialsModal({
isOpen,
onClose,
onSubmit,
export function CredentialsModal({
isOpen,
onClose,
onSubmit,
providerName,
isLoading = false
isLoading = false,
}: CredentialsModalProps) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
@@ -51,10 +51,11 @@ export function CredentialsModal({
<DialogHeader>
<DialogTitle>Insurance Portal Login</DialogTitle>
<DialogDescription>
Enter your credentials for {providerName} insurance portal to check patient eligibility automatically.
Enter your credentials for {providerName} insurance portal to check
patient eligibility automatically.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
@@ -68,7 +69,7 @@ export function CredentialsModal({
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
@@ -97,7 +98,7 @@ export function CredentialsModal({
</Button>
</div>
</div>
<DialogFooter className="flex gap-2">
<Button
type="button"
@@ -118,4 +119,4 @@ export function CredentialsModal({
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,5 +1,14 @@
import { Link, useLocation } from "wouter";
import { LayoutDashboard, Users, Calendar, Settings, FileCheck, Shield, CreditCard, FolderOpen } from "lucide-react";
import {
LayoutDashboard,
Users,
Calendar,
Settings,
FileCheck,
Shield,
CreditCard,
FolderOpen,
} from "lucide-react";
import { cn } from "@/lib/utils";
@@ -62,29 +71,36 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
)}
>
<div className="p-4 border-b border-gray-200 flex items-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-5 w-5 text-primary"
>
<path d="M12 14c-1.65 0-3-1.35-3-3V5c0-1.65 1.35-3 3-3s3 1.35 3 3v6c0 1.65-1.35 3-3 3Z" />
<path d="M19 14v-4a7 7 0 0 0-14 0v4" />
<path d="M12 19c-5 0-8-2-9-5.5m18 0c-1 3.5-4 5.5-9 5.5Z" />
</svg>
<h1 className="text-lg font-medium text-primary">DentalConnect</h1>
</div>
<div className="p-2">
<nav>
{navItems.map((item) => (
<div key={item.path}>
<Link
to={item.path}
onClick={() => setIsMobileOpen(false)}
>
<div className={cn(
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
location === item.path
? "text-primary font-medium border-l-2 border-primary"
: "text-gray-600 hover:bg-gray-100"
)}>
<Link to={item.path} onClick={() => setIsMobileOpen(false)}>
<div
className={cn(
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
location === item.path
? "text-primary font-medium border-l-2 border-primary"
: "text-gray-600 hover:bg-gray-100"
)}
>
{item.icon}
<span>{item.name}</span>
</div>

View File

@@ -1,4 +1,4 @@
import { Bell, Menu} from "lucide-react";
import { Bell, Menu } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useAuth } from "@/hooks/use-auth";
@@ -11,8 +11,6 @@ import {
} from "@/components/ui/dropdown-menu";
import { useLocation } from "wouter";
interface TopAppBarProps {
toggleMobileMenu: () => void;
}
@@ -33,34 +31,39 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
<header className="bg-white shadow-sm z-10">
<div className="flex items-center justify-between h-16 px-4">
<div className="flex items-center">
<Button
variant="ghost"
size="icon"
className="md:hidden mr-2"
<Button
variant="ghost"
size="icon"
className="md:hidden mr-2"
onClick={toggleMobileMenu}
>
<Menu className="h-5 w-5" />
</Button>
<h1 className="md:hidden text-lg font-medium text-primary">DentalConnect</h1>
<h1 className="md:hidden text-lg font-medium text-primary">
DentalConnect
</h1>
</div>
<div className="hidden md:flex md:flex-1 items-center justify-center">
{/* Search bar removed */}
</div>
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="icon"
<Button
variant="ghost"
size="icon"
className="relative p-0 h-9 w-9 rounded-full"
>
<Bell className="h-5 w-5" />
<span className="absolute top-0 right-0 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></span>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative p-0 h-8 w-8 rounded-full">
<Button
variant="ghost"
className="relative p-0 h-8 w-8 rounded-full"
>
<Avatar className="h-8 w-8">
<AvatarImage src="" alt={user?.username} />
<AvatarFallback className="bg-primary text-white">
@@ -72,8 +75,9 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
<DropdownMenuContent align="end">
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
<DropdownMenuItem>My Profile</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLocation("/settings")}>
Account Settings</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLocation("/settings")}>
Account Settings
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
Log out

View File

@@ -14,39 +14,10 @@ import {
DialogContent,
DialogFooter,
} from "@/components/ui/dialog";
import { PatientForm, PatientFormRef } from "./patient-form";
import { useToast } from "@/hooks/use-toast";
import { PatientForm, PatientFormRef } from "./patient-form";
import { X, Calendar } from "lucide-react";
import { useLocation } from "wouter";
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>;
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>;
import { InsertPatient, Patient, UpdatePatient } from "@repo/db/types";
interface AddPatientModalProps {
open: boolean;
@@ -108,12 +79,11 @@ export const AddPatientModal = forwardRef<
};
const handleSaveAndSchedule = () => {
setSaveAndSchedule(true);
if (patientFormRef.current) {
patientFormRef.current.submit();
}
};
setSaveAndSchedule(true);
if (patientFormRef.current) {
patientFormRef.current.submit();
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>

View File

@@ -1,7 +1,5 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { useAuth } from "@/hooks/use-auth";
import {
Form,
@@ -32,34 +30,14 @@ import { format } from "date-fns";
import { Button } from "../ui/button";
import { cn } from "@/lib/utils";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
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,
userId: 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>;
import {
InsertPatient,
insertPatientSchema,
Patient,
UpdatePatient,
updatePatientSchema,
} from "@repo/db/types";
import { z } from "zod";
interface PatientFormProps {
patient?: Patient;

View File

@@ -85,25 +85,25 @@ export function PatientSearch({
<div className="relative flex-1">
{searchBy === "dob" ? (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
onKeyDown={(e) => {
if (e.key === "Enter") handleSearch();
}}
className={cn(
"w-full pl-3 pr-20 text-left font-normal",
!searchTerm && "text-muted-foreground"
)}
>
{searchTerm ? (
format(new Date(searchTerm), "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverTrigger asChild>
<Button
variant="outline"
onKeyDown={(e) => {
if (e.key === "Enter") handleSearch();
}}
className={cn(
"w-full pl-3 pr-20 text-left font-normal",
!searchTerm && "text-muted-foreground"
)}
>
{searchTerm ? (
format(new Date(searchTerm), "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-4">
<Calendar
mode="single"

View File

@@ -18,8 +18,6 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { z } from "zod";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useMutation, useQuery } from "@tanstack/react-query";
import LoadingScreen from "../ui/LoadingScreen";
@@ -39,25 +37,7 @@ import { useDebounce } from "use-debounce";
import { cn } from "@/lib/utils";
import { Checkbox } from "../ui/checkbox";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const updatePatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.omit({
id: true,
createdAt: true,
userId: true,
})
.partial();
type UpdatePatient = z.infer<typeof updatePatientSchema>;
import { Patient, UpdatePatient } from "@repo/db/types";
interface PatientApiResponse {
patients: Patient[];
@@ -114,7 +94,7 @@ export function PatientTable({
const handleSelectPatient = (patient: Patient) => {
const isSelected = selectedPatientId === patient.id;
const newSelectedId = isSelected ? null : patient.id;
setSelectedPatientId(newSelectedId);
setSelectedPatientId(Number(newSelectedId));
if (onSelectPatient) {
onSelectPatient(isSelected ? null : patient);
@@ -258,7 +238,7 @@ export function PatientTable({
if (currentPatient && user) {
const { id, ...sanitizedPatient } = patient;
updatePatientMutation.mutate({
id: currentPatient.id,
id: Number(currentPatient.id),
patient: sanitizedPatient,
});
} else {
@@ -288,7 +268,7 @@ export function PatientTable({
const handleConfirmDeletePatient = async () => {
if (currentPatient) {
deletePatientMutation.mutate(currentPatient.id);
deletePatientMutation.mutate(Number(currentPatient.id));
} else {
toast({
title: "Error",
@@ -424,7 +404,7 @@ export function PatientTable({
<TableCell>
<div className="flex items-center">
<Avatar
className={`h-10 w-10 ${getAvatarColor(patient.id)}`}
className={`h-10 w-10 ${getAvatarColor(Number(patient.id))}`}
>
<AvatarFallback className="text-white">
{getInitials(patient.firstName, patient.lastName)}
@@ -436,7 +416,7 @@ export function PatientTable({
{patient.firstName} {patient.lastName}
</div>
<div className="text-sm text-gray-500">
PID-{patient.id.toString().padStart(4, "0")}
PID-{patient.id?.toString().padStart(4, "0")}
</div>
</div>
</div>
@@ -505,16 +485,16 @@ export function PatientTable({
</Button>
)}
{allowNewClaim && (
<Button
variant="ghost"
size="icon"
onClick={() => onNewClaim?.(patient.id)}
className="text-green-600 hover:text-green-800 hover:bg-green-50"
aria-label="New Claim"
>
<FileCheck className="h-5 w-5" />
</Button>
)}
<Button
variant="ghost"
size="icon"
onClick={() => onNewClaim?.(Number(patient.id))}
className="text-green-600 hover:text-green-800 hover:bg-green-50"
aria-label="New Claim"
>
<FileCheck className="h-5 w-5" />
</Button>
)}
{allowView && (
<Button
variant="ghost"
@@ -558,7 +538,7 @@ export function PatientTable({
{currentPatient.firstName} {currentPatient.lastName}
</h3>
<p className="text-gray-500">
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
Patient ID: {currentPatient.id?.toString().padStart(4, "0")}
</p>
</div>
</div>
@@ -587,8 +567,10 @@ export function PatientTable({
: "text-red-600"
} font-medium`}
>
{currentPatient.status.charAt(0).toUpperCase() +
currentPatient.status.slice(1)}
{currentPatient.status
? currentPatient.status.charAt(0).toUpperCase() +
currentPatient.status.slice(1)
: "Unknown"}
</span>
</p>
</div>
@@ -706,7 +688,7 @@ export function PatientTable({
isOpen={isDeletePatientOpen}
onConfirm={handleConfirmDeletePatient}
onCancel={() => setIsDeletePatientOpen(false)}
entityName={currentPatient?.name}
entityName={currentPatient?.firstName}
/>
{/* Pagination */}
@@ -731,25 +713,25 @@ export function PatientTable({
}
/>
</PaginationItem>
{getPageNumbers(currentPage, totalPages).map((page, idx) => (
<PaginationItem key={idx}>
{page === "..." ? (
<span className="px-2 text-gray-500">...</span>
) : (
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(page as number);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem key={idx}>
{page === "..." ? (
<span className="px-2 text-gray-500">...</span>
) : (
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(page as number);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem>
<PaginationNext

View File

@@ -6,11 +6,20 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { formatDateToHumanReadable } from "@/utils/dateUtils";
import {
formatDateToHumanReadable,
formatLocalDate,
parseLocalDate,
} from "@/utils/dateUtils";
import React, { useState } from "react";
import { paymentStatusOptions, PaymentWithExtras } from "@repo/db/types";
import { PaymentStatus, PaymentMethod } from "@repo/db/types";
import {
PaymentStatus,
paymentStatusOptions,
PaymentMethod,
paymentMethodOptions,
PaymentWithExtras,
NewTransactionPayload,
} from "@repo/db/types";
import {
Select,
SelectContent,
@@ -26,7 +35,7 @@ type PaymentEditModalProps = {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
onClose: () => void;
onEditServiceLine: (updatedPayment: PaymentWithExtras) => void;
onEditServiceLine: (payload: NewTransactionPayload) => void;
payment: PaymentWithExtras | null;
};
@@ -40,143 +49,88 @@ export default function PaymentEditModal({
if (!payment) return null;
const [expandedLineId, setExpandedLineId] = useState<number | null>(null);
const [updatedPaidAmounts, setUpdatedPaidAmounts] = useState<
Record<number, number>
>({});
const [updatedAdjustedAmounts, setUpdatedAdjustedAmounts] = useState<
Record<number, number>
>({});
const [updatedNotes, setUpdatedNotes] = useState<Record<number, string>>({});
const [updatedPaymentStatus, setUpdatedPaymentStatus] =
useState<PaymentStatus>(payment?.status ?? "PENDING");
const [updatedTransactions, setUpdatedTransactions] = useState(
() => payment?.transactions ?? []
const [paymentStatus, setPaymentStatus] = React.useState<PaymentStatus>(
payment.status
);
type DraftPaymentData = {
paidAmount: number;
adjustedAmount?: number;
notes?: string;
paymentStatus?: PaymentStatus;
payerName?: string;
method: PaymentMethod;
receivedDate: string;
};
const [serviceLineDrafts, setServiceLineDrafts] = useState<Record<number, any>>({});
const totalPaid = payment.transactions.reduce(
(sum, tx) =>
sum +
tx.serviceLinePayments.reduce((s, sp) => s + Number(sp.paidAmount), 0),
0
);
const totalBilled = payment.claim.serviceLines.reduce(
(sum, line) => sum + line.billedAmount,
0
);
const totalDue = totalBilled - totalPaid;
const [formState, setFormState] = useState(() => {
return {
serviceLineId: 0,
transactionId: "",
paidAmount: 0,
adjustedAmount: 0,
method: paymentMethodOptions[1] as PaymentMethod,
receivedDate: formatLocalDate(new Date()),
payerName: "",
notes: "",
};
});
const handleEditServiceLine = (lineId: number) => {
setExpandedLineId(lineId === expandedLineId ? null : lineId);
};
const handleFieldChange = (lineId: number, field: string, value: any) => {
setServiceLineDrafts((prev) => ({
...prev,
[lineId]: {
...prev[lineId],
[field]: value,
},
}));
};
// const handleSavePayment = (lineId: number) => {
// const newPaidAmount = updatedPaidAmounts[lineId];
// const newAdjustedAmount = updatedAdjustedAmounts[lineId] ?? 0;
// const newNotes = updatedNotes[lineId] ?? "";
// if (newPaidAmount == null || isNaN(newPaidAmount)) return;
// const updatedTxs = updatedTransactions.map((tx) => ({
// ...tx,
// serviceLinePayments: tx.serviceLinePayments.map((sp) =>
// sp.serviceLineId === lineId
// ? {
// ...sp,
// paidAmount: new Decimal(newPaidAmount),
// adjustedAmount: new Decimal(newAdjustedAmount),
// notes: newNotes,
// }
// : sp
// ),
// }));
// const updatedPayment: PaymentWithExtras = {
// ...payment,
// transactions: updatedTxs,
// status: updatedPaymentStatus,
// };
// setUpdatedTransactions(updatedTxs);
// onEditServiceLine(updatedPayment);
// setExpandedLineId(null);
// };
const handleSavePayment = async (lineId: number) => {
const data = serviceLineDrafts[lineId];
if (!data || !data.paidAmount || !data.method || !data.receivedDate) {
console.log("please fill al")
if (expandedLineId === lineId) {
// Closing current line
setExpandedLineId(null);
return;
}
const transactionPayload = {
// Find line data
const line = payment.claim.serviceLines.find((sl) => sl.id === lineId);
if (!line) return;
// updating form to show its data, while expanding.
setFormState({
serviceLineId: line.id,
transactionId: "",
paidAmount: Number(line.totalDue) > 0 ? Number(line.totalDue) : 0,
adjustedAmount: 0,
method: paymentMethodOptions[1] as PaymentMethod,
receivedDate: formatLocalDate(new Date()),
payerName: "",
notes: "",
});
setExpandedLineId(lineId);
};
const updateField = (field: string, value: any) => {
setFormState((prev) => ({
...prev,
[field]: value,
}));
};
const handleSavePayment = async () => {
if (!formState.serviceLineId) {
toast({ title: "Error", description: "No service line selected." });
return;
}
const payload: NewTransactionPayload = {
paymentId: payment.id,
amount: data.paidAmount + (data.adjustedAmount ?? 0),
method: data.method,
payerName: data.payerName,
notes: data.notes,
receivedDate: new Date(data.receivedDate),
serviceLinePayments: [
status: paymentStatus,
serviceLineTransactions: [
{
serviceLineId: lineId,
paidAmount: data.paidAmount,
adjustedAmount: data.adjustedAmount ?? 0,
notes: data.notes,
serviceLineId: formState.serviceLineId,
transactionId: formState.transactionId || undefined,
paidAmount: Number(formState.paidAmount),
adjustedAmount: Number(formState.adjustedAmount) || 0,
method: formState.method,
receivedDate: parseLocalDate(formState.receivedDate),
payerName: formState.payerName || undefined,
notes: formState.notes || undefined,
},
],
};
try {
await onEditServiceLine(transactionPayload);
await onEditServiceLine(payload);
setExpandedLineId(null);
onClose();
} catch (err) {
console.log(err)
console.error(err);
toast({ title: "Error", description: "Failed to save payment." });
}
};
const renderInput = (label: string, type: string, lineId: number, field: string, step?: string) => (
<div className="space-y-1">
<label className="text-sm font-medium">{label}</label>
<Input
type={type}
step={step}
value={serviceLineDrafts[lineId]?.[field] ?? ""}
onChange={(e) =>
handleFieldChange(lineId, field, type === "number" ? parseFloat(e.target.value) : e.target.value)
}
/>
</div>
);
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
@@ -200,6 +154,10 @@ export default function PaymentEditModal({
Service Date:{" "}
{formatDateToHumanReadable(payment.claim.serviceDate)}
</p>
<p>
<span className="text-gray-500">Notes:</span>{" "}
{payment.notes || "N/A"}
</p>
</div>
{/* Payment Summary */}
@@ -209,22 +167,22 @@ export default function PaymentEditModal({
<div className="mt-2 space-y-1">
<p>
<span className="text-gray-500">Total Billed:</span> $
{totalBilled.toFixed(2)}
{payment.totalBilled.toNumber().toFixed(2)}
</p>
<p>
<span className="text-gray-500">Total Paid:</span> $
{totalPaid.toFixed(2)}
{payment.totalPaid.toNumber().toFixed(2)}
</p>
<p>
<span className="text-gray-500">Total Due:</span> $
{totalDue.toFixed(2)}
{payment.totalDue.toNumber().toFixed(2)}
</p>
<div className="pt-2">
<label className="text-sm text-gray-600">Status</label>
<Select
value={updatedPaymentStatus}
value={paymentStatus}
onValueChange={(value: PaymentStatus) =>
setUpdatedPaymentStatus(value)
setPaymentStatus(value)
}
>
<SelectTrigger>
@@ -246,18 +204,16 @@ export default function PaymentEditModal({
<h4 className="font-medium text-gray-900">Metadata</h4>
<div className="mt-2 space-y-1">
<p>
<span className="text-gray-500">Received Date:</span>{" "}
{payment.receivedDate
? formatDateToHumanReadable(payment.receivedDate)
<span className="text-gray-500">Created At:</span>{" "}
{payment.createdAt
? formatDateToHumanReadable(payment.createdAt)
: "N/A"}
</p>
<p>
<span className="text-gray-500">Method:</span>{" "}
{payment.paymentMethod ?? "N/A"}
</p>
<p>
<span className="text-gray-500">Notes:</span>{" "}
{payment.notes || "N/A"}
<span className="text-gray-500">Last Upadated At:</span>{" "}
{payment.updatedAt
? formatDateToHumanReadable(payment.updatedAt)
: "N/A"}
</p>
</div>
</div>
@@ -270,22 +226,6 @@ export default function PaymentEditModal({
{payment.claim.serviceLines.length > 0 ? (
<>
{payment.claim.serviceLines.map((line) => {
const linePayments = payment.transactions.flatMap((tx) =>
tx.serviceLinePayments.filter(
(sp) => sp.serviceLineId === line.id
)
);
const paidAmount = linePayments.reduce(
(sum, sp) => sum + Number(sp.paidAmount),
0
);
const adjusted = linePayments.reduce(
(sum, sp) => sum + Number(sp.adjustedAmount),
0
);
const due = line.billedAmount - paidAmount;
return (
<div
key={line.id}
@@ -297,19 +237,19 @@ export default function PaymentEditModal({
</p>
<p>
<span className="text-gray-500">Billed:</span> $
{line.billedAmount.toFixed(2)}
{line.totalBilled.toFixed(2)}
</p>
<p>
<span className="text-gray-500">Paid:</span> $
{paidAmount.toFixed(2)}
{line.totalPaid.toFixed(2)}
</p>
<p>
<span className="text-gray-500">Adjusted:</span> $
{adjusted.toFixed(2)}
{line.totalAdjusted.toFixed(2)}
</p>
<p>
<span className="text-gray-500">Due:</span> $
{due.toFixed(2)}
{line.totalDue.toFixed(2)}
</p>
<div className="pt-2">
@@ -337,12 +277,12 @@ export default function PaymentEditModal({
type="number"
step="0.01"
placeholder="Paid Amount"
defaultValue={paidAmount}
defaultValue={formState.paidAmount}
onChange={(e) =>
setUpdatedPaidAmounts({
...updatedPaidAmounts,
[line.id]: parseFloat(e.target.value),
})
updateField(
"paidAmount",
parseFloat(e.target.value)
)
}
/>
</div>
@@ -358,12 +298,65 @@ export default function PaymentEditModal({
type="number"
step="0.01"
placeholder="Adjusted Amount"
defaultValue={adjusted}
defaultValue={formState.adjustedAmount}
onChange={(e) =>
setUpdatedAdjustedAmounts({
...updatedAdjustedAmounts,
[line.id]: parseFloat(e.target.value),
})
updateField(
"adjustedAmount",
parseFloat(e.target.value)
)
}
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium">
Payment Method
</label>
<Select
value={formState.method}
onValueChange={(value: PaymentMethod) =>
updateField("method", value)
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{paymentMethodOptions.map((methodOption) => (
<SelectItem
key={methodOption}
value={methodOption}
>
{methodOption}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<label className="text-sm font-medium">
Received Date
</label>
<Input
type="date"
value={formState.receivedDate}
onChange={(e) =>
updateField("receivedDate", e.target.value)
}
/>
</div>
<div className="space-y-1">
<label className="text-sm font-medium">
Payer Name
</label>
<Input
type="text"
placeholder="Payer Name"
value={formState.payerName}
onChange={(e) =>
updateField("payerName", e.target.value)
}
/>
</div>
@@ -380,16 +373,13 @@ export default function PaymentEditModal({
type="text"
placeholder="Notes"
onChange={(e) =>
setUpdatedNotes({
...updatedNotes,
[line.id]: e.target.value,
})
updateField("notes", e.target.value)
}
/>
</div>
<Button
size="sm"
onClick={() => handleSavePayment(line.id)}
onClick={() => handleSavePayment()}
>
Save
</Button>
@@ -409,8 +399,8 @@ export default function PaymentEditModal({
<div>
<h4 className="font-medium text-gray-900 pt-6">All Transactions</h4>
<div className="mt-2 space-y-2">
{payment.transactions.length > 0 ? (
payment.transactions.map((tx) => (
{payment.serviceLineTransactions.length > 0 ? (
payment.serviceLineTransactions.map((tx) => (
<div
key={tx.id}
className="border p-3 rounded-md bg-white shadow-sm"
@@ -420,18 +410,27 @@ export default function PaymentEditModal({
{formatDateToHumanReadable(tx.receivedDate)}
</p>
<p>
<span className="text-gray-500">Amount:</span> $
{Number(tx.amount).toFixed(2)}
<span className="text-gray-500">Paid Amount:</span> $
{Number(tx.paidAmount).toFixed(2)}
</p>
<p>
<span className="text-gray-500">Adjusted Amount:</span> $
{Number(tx.adjustedAmount).toFixed(2)}
</p>
<p>
<span className="text-gray-500">Method:</span> {tx.method}
</p>
{tx.serviceLinePayments.map((sp) => (
<p key={sp.id} className="text-sm text-gray-600 ml-2">
Applied ${Number(sp.paidAmount).toFixed(2)} to service
line ID {sp.serviceLineId}
{tx.payerName && (
<p>
<span className="text-gray-500">Payer Name:</span>{" "}
{tx.payerName}
</p>
))}
)}
{tx.notes && (
<p>
<span className="text-gray-500">Notes:</span> {tx.notes}
</p>
)}
</div>
))
) : (

View File

@@ -31,12 +31,11 @@ import {
import { Checkbox } from "@/components/ui/checkbox";
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
import PaymentViewModal from "./payment-view-modal";
import PaymentEditModal from "./payment-edit-modal";
import LoadingScreen from "../ui/LoadingScreen";
import {
ClaimStatus,
ClaimWithServiceLines,
Payment,
NewTransactionPayload,
PaymentWithExtras,
} from "@repo/db/types";
import EditPaymentModal from "./payment-edit-modal";
@@ -118,10 +117,14 @@ export default function PaymentsRecentTable({
});
const updatePaymentMutation = useMutation({
mutationFn: async (payment: PaymentWithExtras) => {
const response = await apiRequest("PUT", `/api/claims/${payment.id}`, {
data: payment,
});
mutationFn: async (data: NewTransactionPayload) => {
const response = await apiRequest(
"PUT",
`/api/claims/${data.paymentId}`,
{
data: data,
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Failed to update Payment");
@@ -288,13 +291,6 @@ export default function PaymentsRecentTable({
}
};
const getTotalBilled = (claim: ClaimWithServiceLines) => {
return claim.serviceLines.reduce(
(sum, line) => sum + (line.billedAmount || 0),
0
);
};
function getPageNumbers(current: number, total: number): (number | "...")[] {
const delta = 2;
const range: (number | "...")[] = [];
@@ -359,19 +355,9 @@ export default function PaymentsRecentTable({
</TableRow>
) : (
paymentsData?.payments.map((payment) => {
const claim = (payment as PaymentWithExtras)
.claim as ClaimWithServiceLines;
const totalBilled = getTotalBilled(claim);
const totalPaid = (payment as PaymentWithExtras).transactions
.flatMap((tx) => tx.serviceLinePayments)
.reduce(
(sum, sp) => sum + (sp.paidAmount?.toNumber?.() ?? 0),
0
);
const outstanding = totalBilled - totalPaid;
const totalBilled = payment.totalBilled.toNumber();
const totalPaid = payment.totalPaid.toNumber();
const totalDue = payment.totalDue.toNumber();
return (
<TableRow key={payment.id}>
@@ -401,9 +387,9 @@ export default function PaymentsRecentTable({
</span>
<span>
<strong>Total Due:</strong>{" "}
{outstanding > 0 ? (
{totalDue > 0 ? (
<span className="text-yellow-600">
${outstanding.toFixed(2)}
${totalDue.toFixed(2)}
</span>
) : (
<span className="text-green-600">Settled</span>

View File

@@ -1,8 +1,5 @@
import { Staff } from "@repo/db/types";
import React, { useState, useEffect } from "react";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { z } from "zod";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
interface StaffFormProps {
initialData?: Partial<Staff>;

View File

@@ -1,15 +1,7 @@
import React, { useState } from "react";
import { z } from "zod";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { Button } from "../ui/button";
import { Delete, Edit } from "lucide-react";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).partial();
import { Staff } from "@repo/db/types";
interface StaffTableProps {
staff: Staff[];
@@ -21,7 +13,6 @@ interface StaffTableProps {
onView: (staff: Staff) => void;
}
export function StaffTable({
staff,
onEdit,
@@ -151,15 +142,13 @@ export function StaffTable({
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<Button
onClick={() =>
staff !== undefined && onDelete(staff)
}
onClick={() => staff !== undefined && onDelete(staff)}
className="text-red-600 hover:text-red-900"
aria-label="Delete Staff"
variant="ghost"
size="icon"
>
<Delete/>
<Delete />
</Button>
<Button
onClick={() => staff.id !== undefined && onEdit(staff)}
@@ -170,7 +159,6 @@ export function StaffTable({
>
<Edit className="h-4 w-4" />
</Button>
</td>
</tr>
);
@@ -235,7 +223,9 @@ export function StaffTable({
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
}}
className={`relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 ${
currentPage === totalPages ? "pointer-events-none opacity-50" : ""
currentPage === totalPages
? "pointer-events-none opacity-50"
: ""
}`}
>
Next

View File

@@ -1,10 +1,7 @@
import { useState, useEffect } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { format, addDays, startOfToday, addMinutes } from "date-fns";
import {
parseLocalDate,
formatLocalDate,
} from "@/utils/dateUtils";
import { parseLocalDate, formatLocalDate } 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";
@@ -19,12 +16,7 @@ import {
Trash2,
} from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { z } from "zod";
import { Calendar } from "@/components/ui/calendar";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import {
@@ -40,35 +32,12 @@ import { Menu, Item, useContextMenu } from "react-contexify";
import "react-contexify/ReactContexify.css";
import { useLocation } from "wouter";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
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,
userId: 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>;
import {
Appointment,
InsertAppointment,
Patient,
UpdateAppointment,
} from "@repo/db/types";
// Define types for scheduling
interface TimeSlot {
@@ -287,7 +256,11 @@ export default function AppointmentsPage() {
// Create/upsert appointment mutation
const createAppointmentMutation = useMutation({
mutationFn: async (appointment: InsertAppointment) => {
const res = await apiRequest("POST", "/api/appointments/upsert", appointment);
const res = await apiRequest(
"POST",
"/api/appointments/upsert",
appointment
);
return await res.json();
},
onSuccess: () => {
@@ -436,8 +409,8 @@ export default function AppointmentsPage() {
const formattedDate = format(selectedDate, "yyyy-MM-dd");
const selectedDateAppointments = appointments.filter((appointment) => {
const dateObj = parseLocalDate(appointment.date)
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
const dateObj = parseLocalDate(appointment.date);
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
});
// Process appointments for the scheduler view
@@ -466,8 +439,8 @@ export default function AppointmentsPage() {
...apt,
patientName,
staffId,
status: apt.status ?? null,
date: formatLocalDate(parseLocalDate(apt.date))
status: apt.status ?? null,
date: formatLocalDate(parseLocalDate(apt.date)),
};
return processed;

View File

@@ -1,7 +1,5 @@
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { useEffect, useState } from "react";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
@@ -16,41 +14,17 @@ import {
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Checkbox } from "@/components/ui/checkbox";
import { Card} from "@/components/ui/card";
import { Card } from "@/components/ui/card";
import { CheckCircle, Torus } from "lucide-react";
import { CheckedState } from "@radix-ui/react-checkbox";
import LoadingScreen from "@/components/ui/LoadingScreen";
import { useLocation } from "wouter";
const insertUserSchema = (
UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).pick({
username: true,
password: true,
});
const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
rememberMe: z.boolean().optional(),
});
const registerSchema = (insertUserSchema as unknown as z.ZodObject<any>)
.extend({
confirmPassword: z.string().min(6, {
message: "Password must be at least 6 characters long",
}),
agreeTerms: z.literal(true, {
errorMap: () => ({
message: "You must agree to the terms and conditions",
}),
}),
})
.refine((data: any) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
type LoginFormValues = z.infer<typeof loginSchema>;
type RegisterFormValues = z.infer<typeof registerSchema>;
import {
LoginFormValues,
loginSchema,
RegisterFormValues,
registerSchema,
} from "@repo/db/types";
export default function AuthPage() {
const [activeTab, setActiveTab] = useState<string>("login");

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo } from "react";
import { useMutation} 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 {
@@ -12,13 +12,7 @@ import {
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 { parse } from "date-fns";
import { z } from "zod";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useLocation } from "wouter";
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
@@ -27,58 +21,16 @@ import {
clearTaskStatus,
} from "@/redux/slices/seleniumClaimSubmitTaskSlice";
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
import { formatLocalDate} 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";
//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>;
import {
Claim,
InsertAppointment,
InsertPatient,
UpdateAppointment,
UpdatePatient,
} from "@repo/db/types";
export default function ClaimsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);

View File

@@ -27,42 +27,13 @@ import {
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<typeof AppointmentUncheckedCreateInputObjectSchema>;
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>;
import {
Appointment,
InsertAppointment,
InsertPatient,
Patient,
UpdateAppointment,
} from "@repo/db/types";
// Type for the ref to access modal methods
type AddPatientModalRef = {
@@ -156,7 +127,11 @@ export default function Dashboard() {
// Create/upsert appointment mutation
const createAppointmentMutation = useMutation({
mutationFn: async (appointment: InsertAppointment) => {
const res = await apiRequest("POST", "/api/appointments/upsert", appointment);
const res = await apiRequest(
"POST",
"/api/appointments/upsert",
appointment
);
return await res.json();
},
onSuccess: () => {

View File

@@ -11,26 +11,11 @@ import { Button } from "@/components/ui/button";
import { toast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { Eye, Trash, Download, FolderOpen } from "lucide-react";
import {
PatientUncheckedCreateInputObjectSchema,
PdfFileUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
import { PatientTable } from "@/components/patients/patient-table";
import { z } from "zod";
import { Sidebar } from "@/components/layout/sidebar";
import { TopAppBar } from "@/components/layout/top-app-bar";
const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
appointments: true,
});
type Patient = z.infer<typeof PatientSchema>;
const PdfFileSchema =
PdfFileUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>;
type PdfFile = z.infer<typeof PdfFileSchema>;
import { Patient, PdfFile } from "@repo/db/types";
export default function DocumentsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -44,11 +29,10 @@ export default function DocumentsPage() {
const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev);
useEffect(() => {
setSelectedGroupId(null);
setFileBlobUrl(null);
setSelectedPdfId(null);
}, [selectedPatient]);
setSelectedGroupId(null);
setFileBlobUrl(null);
setSelectedPdfId(null);
}, [selectedPatient]);
const { data: groups = [] } = useQuery({
queryKey: ["groups", selectedPatient?.id],
@@ -99,7 +83,7 @@ export default function DocumentsPage() {
const handleConfirmDeletePdf = () => {
if (currentPdf) {
deletePdfMutation.mutate(currentPdf.id);
deletePdfMutation.mutate(Number(currentPdf.id));
} else {
toast({
title: "Error",

View File

@@ -13,10 +13,8 @@ import {
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { CalendarIcon, CheckCircle, LoaderCircleIcon } from "lucide-react";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { useAuth } from "@/hooks/use-auth";
import { useToast } from "@/hooks/use-toast";
import { z } from "zod";
import { PatientTable } from "@/components/patients/patient-table";
import { format } from "date-fns";
import { Calendar } from "@/components/ui/calendar";
@@ -34,22 +32,7 @@ import {
} from "@/redux/slices/seleniumEligibilityCheckTaskSlice";
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
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,
userId: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
import { InsertPatient, Patient } from "@repo/db/types";
export default function InsuranceEligibilityPage() {
const { user } = useAuth();

View File

@@ -1,5 +1,5 @@
import { useState, useMemo, useRef } from "react";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useState, useRef } from "react";
import { useMutation } from "@tanstack/react-query";
import { TopAppBar } from "@/components/layout/top-app-bar";
import { Sidebar } from "@/components/layout/sidebar";
import { PatientTable } from "@/components/patients/patient-table";
@@ -15,28 +15,11 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import { z } from "zod";
import useExtractPdfData from "@/hooks/use-extractPdfData";
import { useLocation } from "wouter";
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,
userId: true,
});
type InsertPatient = z.infer<typeof insertPatientSchema>;
import { InsertPatient, Patient } from "@repo/db/types";
// Type for the ref to access modal methods
type AddPatientModalRef = {
@@ -105,7 +88,6 @@ export default function PatientsPage() {
const isLoading = addPatientMutation.isPending;
// File upload handling
const handleFileUpload = (file: File) => {
setIsUploading(true);

View File

@@ -13,15 +13,9 @@ import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/hooks/use-auth";
import {
CreditCard,
Clock,
CheckCircle,
AlertCircle,
DollarSign,
Receipt,
Plus,
ArrowDown,
ReceiptText,
Upload,
Image,
X,
@@ -53,56 +47,7 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
import {
AppointmentUncheckedCreateInputObjectSchema,
PatientUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas";
import { z } from "zod";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
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>;
//patient types
type Patient = z.infer<typeof PatientUncheckedCreateInputObjectSchema>;
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>;
import { Appointment, Patient } from "@repo/db/types";
export default function PaymentsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);

View File

@@ -6,10 +6,9 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/hooks/use-auth";
// import { Patient, Appointment } from "@repo/db/shared/schemas";
import { Patient, Appointment } from "@repo/db/shared/schemas";
import { Plus, ClipboardCheck, Clock, CheckCircle, AlertCircle } from "lucide-react";
import { format } from "date-fns";
import { Appointment, Patient } from "@repo/db/types";
export default function PreAuthorizationsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -138,7 +137,7 @@ export default function PreAuthorizationsPage() {
key={patient.id}
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
onClick={() => {
setSelectedPatient(patient.id);
setSelectedPatient(Number(patient.id));
handleNewPreAuth(
patient.id,
dentalProcedures[Math.floor(Math.random() * 3)].name

View File

@@ -5,16 +5,13 @@ import { Sidebar } from "@/components/layout/sidebar";
import { StaffTable } from "@/components/staffs/staff-table";
import { useToast } from "@/hooks/use-toast";
import { Card, CardContent } from "@/components/ui/card";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import { z } from "zod";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { StaffForm } from "@/components/staffs/staff-form";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
import { CredentialTable } from "@/components/settings/insuranceCredTable";
import { useAuth } from "@/hooks/use-auth";
import { Staff } from "@repo/db/types";
// Correctly infer Staff type from zod schema
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
export default function SettingsPage() {
const { toast } = useToast();