types updated
This commit is contained in:
39
ReadmeForDBmigrate.txt
Normal file
39
ReadmeForDBmigrate.txt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
while updating whole schema for payment,
|
||||||
|
|
||||||
|
firstly just update this line in schema: and follow all steps. then update other schema from github.
|
||||||
|
|
||||||
|
totalBilled Decimal @db.Decimal(10, 2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. firstly create backup:
|
||||||
|
|
||||||
|
pg_dump -U your_db_user -h localhost -p 5432 your_db_name > backup_before_totalBilled.sql
|
||||||
|
$ pg_dump -U postgres -h localhost -p 5432 dentalapp > backup_before_totalBilled.sql
|
||||||
|
|
||||||
|
2. - now update the schema:
|
||||||
|
totalBilled Decimal @db.Decimal(10, 2)
|
||||||
|
|
||||||
|
|
||||||
|
3. create migration not apply yet
|
||||||
|
npx prisma migrate dev --create-only --name rename-billedamount-to-totalbilled
|
||||||
|
|
||||||
|
|
||||||
|
4. edit migration.sql file:
|
||||||
|
|
||||||
|
replace whatever prisma put there:
|
||||||
|
|
||||||
|
ALTER TABLE "public"."ServiceLine"
|
||||||
|
RENAME COLUMN "billedAmount" TO "totalBilled";
|
||||||
|
|
||||||
|
ALTER TABLE "public"."ServiceLine"
|
||||||
|
ALTER COLUMN "totalBilled" TYPE DECIMAL(10,2) USING "totalBilled"::DECIMAL(10,2);
|
||||||
|
|
||||||
|
5. npx prisma migrate dev
|
||||||
|
|
||||||
|
|
||||||
|
6. if anythign goes wrong ,do restore backup.
|
||||||
|
psql -U your_db_user -h localhost -p 5432 your_db_name < backup_before_totalBilled.sql
|
||||||
|
|
||||||
|
|
||||||
@@ -2,73 +2,8 @@ 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 {
|
|
||||||
ClaimUncheckedCreateInputObjectSchema,
|
|
||||||
PaymentUncheckedCreateInputObjectSchema,
|
|
||||||
PaymentTransactionCreateInputObjectSchema,
|
|
||||||
ServiceLinePaymentCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import { Prisma } from "@repo/db/generated/prisma";
|
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
import { insertPaymentSchema, updatePaymentSchema } from "@repo/db/types";
|
||||||
// Base Payment type
|
|
||||||
type Payment = z.infer<typeof PaymentUncheckedCreateInputObjectSchema>;
|
|
||||||
type PaymentTransaction = z.infer<
|
|
||||||
typeof PaymentTransactionCreateInputObjectSchema
|
|
||||||
>;
|
|
||||||
type ServiceLinePayment = z.infer<
|
|
||||||
typeof ServiceLinePaymentCreateInputObjectSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
const insertPaymentSchema = (
|
|
||||||
PaymentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
});
|
|
||||||
type InsertPayment = z.infer<typeof insertPaymentSchema>;
|
|
||||||
|
|
||||||
const updatePaymentSchema = (
|
|
||||||
PaymentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
)
|
|
||||||
.omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
})
|
|
||||||
.partial();
|
|
||||||
type UpdatePayment = z.infer<typeof updatePaymentSchema>;
|
|
||||||
|
|
||||||
type PaymentWithExtras = Prisma.PaymentGetPayload<{
|
|
||||||
include: {
|
|
||||||
transactions: true;
|
|
||||||
servicePayments: true;
|
|
||||||
claim: true;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// Claim schema
|
|
||||||
const ClaimSchema = (
|
|
||||||
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
type InsertClaim = z.infer<typeof ClaimSchema>;
|
|
||||||
|
|
||||||
const updateClaimSchema = (
|
|
||||||
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
)
|
|
||||||
.omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
})
|
|
||||||
.partial();
|
|
||||||
|
|
||||||
type UpdateClaim = z.infer<typeof updateClaimSchema>;
|
|
||||||
|
|
||||||
const paymentFilterSchema = z.object({
|
const paymentFilterSchema = z.object({
|
||||||
from: z.string().datetime(),
|
from: z.string().datetime(),
|
||||||
|
|||||||
@@ -1,165 +1,29 @@
|
|||||||
import { prisma as db } from "@repo/db/client";
|
import { prisma as db } from "@repo/db/client";
|
||||||
|
import { PdfCategory } from "@repo/db/generated/prisma";
|
||||||
import {
|
import {
|
||||||
AppointmentUncheckedCreateInputObjectSchema,
|
Appointment,
|
||||||
PatientUncheckedCreateInputObjectSchema,
|
Claim,
|
||||||
UserUncheckedCreateInputObjectSchema,
|
ClaimWithServiceLines,
|
||||||
StaffUncheckedCreateInputObjectSchema,
|
InsertAppointment,
|
||||||
ClaimUncheckedCreateInputObjectSchema,
|
InsertClaim,
|
||||||
InsuranceCredentialUncheckedCreateInputObjectSchema,
|
InsertInsuranceCredential,
|
||||||
PdfFileUncheckedCreateInputObjectSchema,
|
InsertPatient,
|
||||||
PdfGroupUncheckedCreateInputObjectSchema,
|
|
||||||
PdfCategorySchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
|
||||||
InsertPayment,
|
InsertPayment,
|
||||||
|
InsertUser,
|
||||||
|
InsuranceCredential,
|
||||||
|
Patient,
|
||||||
Payment,
|
Payment,
|
||||||
PaymentWithExtras,
|
PaymentWithExtras,
|
||||||
|
PdfFile,
|
||||||
|
PdfGroup,
|
||||||
|
Staff,
|
||||||
|
UpdateAppointment,
|
||||||
|
UpdateClaim,
|
||||||
|
UpdatePatient,
|
||||||
UpdatePayment,
|
UpdatePayment,
|
||||||
|
User,
|
||||||
} from "@repo/db/types";
|
} from "@repo/db/types";
|
||||||
|
|
||||||
//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>;
|
|
||||||
|
|
||||||
//user types
|
|
||||||
type User = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
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 InsertUser = z.infer<typeof insertUserSchema>;
|
|
||||||
type LoginFormValues = z.infer<typeof loginSchema>;
|
|
||||||
type RegisterFormValues = z.infer<typeof registerSchema>;
|
|
||||||
|
|
||||||
// staff types:
|
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
// Claim typse:
|
|
||||||
const insertClaimSchema = (
|
|
||||||
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
});
|
|
||||||
type InsertClaim = z.infer<typeof insertClaimSchema>;
|
|
||||||
|
|
||||||
const updateClaimSchema = (
|
|
||||||
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
)
|
|
||||||
.omit({
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true,
|
|
||||||
})
|
|
||||||
.partial();
|
|
||||||
type UpdateClaim = z.infer<typeof updateClaimSchema>;
|
|
||||||
|
|
||||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
// InsuraneCreds types:
|
|
||||||
type InsuranceCredential = z.infer<
|
|
||||||
typeof InsuranceCredentialUncheckedCreateInputObjectSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
const insertInsuranceCredentialSchema = (
|
|
||||||
InsuranceCredentialUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).omit({ id: true });
|
|
||||||
|
|
||||||
type InsertInsuranceCredential = z.infer<
|
|
||||||
typeof insertInsuranceCredentialSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pdf types:
|
|
||||||
type PdfGroup = z.infer<typeof PdfGroupUncheckedCreateInputObjectSchema>;
|
|
||||||
type PdfFile = z.infer<typeof PdfFileUncheckedCreateInputObjectSchema>;
|
|
||||||
type PdfCategory = z.infer<typeof PdfCategorySchema>;
|
|
||||||
|
|
||||||
export interface ClaimPdfMetadata {
|
|
||||||
id: number;
|
|
||||||
filename: string;
|
|
||||||
uploadedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IStorage {
|
export interface IStorage {
|
||||||
// User methods
|
// User methods
|
||||||
getUser(id: number): Promise<User | undefined>;
|
getUser(id: number): Promise<User | undefined>;
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
import { useState } from "react";
|
import {
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import { AppointmentForm } from "./appointment-form";
|
import { AppointmentForm } from "./appointment-form";
|
||||||
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
import {
|
||||||
|
Appointment,
|
||||||
import {z} from "zod";
|
InsertAppointment,
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
Patient,
|
||||||
|
UpdateAppointment,
|
||||||
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
} from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface AddAppointmentModalProps {
|
interface AddAppointmentModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -42,7 +31,6 @@ export function AddAppointmentModal({
|
|||||||
appointment,
|
appointment,
|
||||||
patients,
|
patients,
|
||||||
}: AddAppointmentModalProps) {
|
}: AddAppointmentModalProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-md">
|
<DialogContent className="max-w-md">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
@@ -33,41 +33,13 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { useDebounce } from "use-debounce";
|
import { useDebounce } from "use-debounce";
|
||||||
import {
|
import {
|
||||||
AppointmentUncheckedCreateInputObjectSchema,
|
Appointment,
|
||||||
PatientUncheckedCreateInputObjectSchema,
|
InsertAppointment,
|
||||||
StaffUncheckedCreateInputObjectSchema,
|
insertAppointmentSchema,
|
||||||
} from "@repo/db/usedSchemas";
|
Patient,
|
||||||
|
Staff,
|
||||||
import { z } from "zod";
|
UpdateAppointment,
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
} from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface AppointmentFormProps {
|
interface AppointmentFormProps {
|
||||||
appointment?: Appointment;
|
appointment?: Appointment;
|
||||||
@@ -211,7 +183,7 @@ export function AppointmentForm({
|
|||||||
const term = debouncedSearchTerm.toLowerCase();
|
const term = debouncedSearchTerm.toLowerCase();
|
||||||
setFilteredPatients(
|
setFilteredPatients(
|
||||||
patients.filter((p) =>
|
patients.filter((p) =>
|
||||||
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`
|
`${p.firstName} ${p.lastName} ${p.phone} ${p.dateOfBirth}`
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(term)
|
.includes(term)
|
||||||
)
|
)
|
||||||
@@ -351,7 +323,7 @@ export function AppointmentForm({
|
|||||||
filteredPatients.map((patient) => (
|
filteredPatients.map((patient) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={patient.id}
|
key={patient.id}
|
||||||
value={patient.id.toString()}
|
value={patient.id?.toString() ?? ""}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
|
|||||||
@@ -25,20 +25,7 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import {
|
import { Appointment, Patient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface AppointmentTableProps {
|
interface AppointmentTableProps {
|
||||||
appointments: Appointment[];
|
appointments: Appointment[];
|
||||||
|
|||||||
@@ -15,29 +15,7 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { z } from "zod";
|
import { ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ClaimEditModalProps = {
|
type ClaimEditModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -223,14 +201,17 @@ export default function ClaimEditModal({
|
|||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Billed Amount:</span> $
|
<span className="text-gray-500">Billed Amount:</span> $
|
||||||
{line.billedAmount.toFixed(2)}
|
{line.totalBilled.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
||||||
Total Billed Amount: $
|
Total Billed Amount: $
|
||||||
{claim.serviceLines
|
{claim.serviceLines
|
||||||
.reduce((total, line) => total + line.billedAmount, 0)
|
.reduce(
|
||||||
|
(total, line) => total + line.totalBilled?.toNumber(),
|
||||||
|
0
|
||||||
|
)
|
||||||
.toFixed(2)}
|
.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} 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 { useQuery } from "@tanstack/react-query";
|
||||||
import { apiRequest } from "@/lib/queryClient";
|
import { apiRequest } from "@/lib/queryClient";
|
||||||
import { MultipleFileUploadZone } from "../file-upload/multiple-file-upload-zone";
|
import { MultipleFileUploadZone } from "../file-upload/multiple-file-upload-zone";
|
||||||
@@ -37,57 +29,16 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import procedureCodes from "../../assets/data/procedureCodes.json";
|
import procedureCodes from "../../assets/data/procedureCodes.json";
|
||||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||||
|
import {
|
||||||
const PatientSchema = (
|
Claim,
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
InputServiceLine,
|
||||||
).omit({
|
InsertAppointment,
|
||||||
appointments: true,
|
Patient,
|
||||||
});
|
Staff,
|
||||||
type Patient = z.infer<typeof PatientSchema>;
|
UpdateAppointment,
|
||||||
|
UpdatePatient,
|
||||||
const updatePatientSchema = (
|
} from "@repo/db/types";
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
import { Decimal } from "decimal.js";
|
||||||
)
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ClaimFormData {
|
interface ClaimFormData {
|
||||||
patientId: number;
|
patientId: number;
|
||||||
@@ -102,7 +53,7 @@ interface ClaimFormData {
|
|||||||
insuranceProvider: string;
|
insuranceProvider: string;
|
||||||
insuranceSiteKey?: string;
|
insuranceSiteKey?: string;
|
||||||
status: string; // default "pending"
|
status: string; // default "pending"
|
||||||
serviceLines: ServiceLine[];
|
serviceLines: InputServiceLine[];
|
||||||
claimId?: number;
|
claimId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,9 +68,6 @@ interface ClaimFormProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
export function ClaimForm({
|
export function ClaimForm({
|
||||||
patientId,
|
patientId,
|
||||||
onHandleAppointmentSubmit,
|
onHandleAppointmentSubmit,
|
||||||
@@ -207,16 +155,21 @@ export function ClaimForm({
|
|||||||
}, [serviceDate]);
|
}, [serviceDate]);
|
||||||
|
|
||||||
// Determine patient date of birth format - required as date extracted from pdfs has different format.
|
// 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 (!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 normalized = formatLocalDate(parseLocalDate(dob));
|
||||||
const datePart = dob?.split("T")[0]; // safe optional chaining
|
|
||||||
if (!datePart) return "";
|
// If it's already MM/DD/YYYY, leave it alone
|
||||||
const [year, month, day] = datePart.split("-");
|
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 `${month}/${day}/${year}`;
|
||||||
}
|
}
|
||||||
return dob;
|
|
||||||
|
return normalized;
|
||||||
};
|
};
|
||||||
|
|
||||||
// MAIN FORM INITIAL STATE
|
// MAIN FORM INITIAL STATE
|
||||||
@@ -226,7 +179,7 @@ export function ClaimForm({
|
|||||||
userId: Number(user?.id),
|
userId: Number(user?.id),
|
||||||
staffId: Number(staff?.id),
|
staffId: Number(staff?.id),
|
||||||
patientName: `${patient?.firstName} ${patient?.lastName}`.trim(),
|
patientName: `${patient?.firstName} ${patient?.lastName}`.trim(),
|
||||||
memberId: patient?.insuranceId,
|
memberId: patient?.insuranceId ?? "",
|
||||||
dateOfBirth: formatDOB(patient?.dateOfBirth),
|
dateOfBirth: formatDOB(patient?.dateOfBirth),
|
||||||
remarks: "",
|
remarks: "",
|
||||||
serviceDate: serviceDate,
|
serviceDate: serviceDate,
|
||||||
@@ -239,7 +192,9 @@ export function ClaimForm({
|
|||||||
oralCavityArea: "",
|
oralCavityArea: "",
|
||||||
toothNumber: "",
|
toothNumber: "",
|
||||||
toothSurface: "",
|
toothSurface: "",
|
||||||
billedAmount: 0,
|
totalBilled: new Decimal(0),
|
||||||
|
totalAdjusted: new Decimal(0),
|
||||||
|
totalPaid: new Decimal(0),
|
||||||
})),
|
})),
|
||||||
uploadedFiles: [],
|
uploadedFiles: [],
|
||||||
});
|
});
|
||||||
@@ -251,10 +206,10 @@ export function ClaimForm({
|
|||||||
`${patient.firstName || ""} ${patient.lastName || ""}`.trim();
|
`${patient.firstName || ""} ${patient.lastName || ""}`.trim();
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
patientId: Number(patient.id),
|
||||||
patientName: fullName,
|
patientName: fullName,
|
||||||
dateOfBirth: formatDOB(patient.dateOfBirth),
|
dateOfBirth: formatDOB(patient.dateOfBirth),
|
||||||
memberId: patient.insuranceId || "",
|
memberId: patient.insuranceId || "",
|
||||||
patientId: patient.id,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [patient]);
|
}, [patient]);
|
||||||
@@ -266,16 +221,16 @@ export function ClaimForm({
|
|||||||
|
|
||||||
const updateServiceLine = (
|
const updateServiceLine = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof ServiceLine,
|
field: keyof InputServiceLine,
|
||||||
value: any
|
value: any
|
||||||
) => {
|
) => {
|
||||||
const updatedLines = [...form.serviceLines];
|
const updatedLines = [...form.serviceLines];
|
||||||
|
|
||||||
if (updatedLines[index]) {
|
if (updatedLines[index]) {
|
||||||
if (field === "billedAmount") {
|
if (field === "totalBilled") {
|
||||||
const num = typeof value === "string" ? parseFloat(value) : value;
|
const num = typeof value === "string" ? parseFloat(value) : value;
|
||||||
const rounded = Math.round((isNaN(num) ? 0 : num) * 100) / 100;
|
const rounded = Math.round((isNaN(num) ? 0 : num) * 100) / 100;
|
||||||
updatedLines[index][field] = rounded;
|
updatedLines[index][field] = new Decimal(rounded);
|
||||||
} else {
|
} else {
|
||||||
updatedLines[index][field] = value;
|
updatedLines[index][field] = value;
|
||||||
}
|
}
|
||||||
@@ -672,16 +627,20 @@ export function ClaimForm({
|
|||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="$0.00"
|
placeholder="$0.00"
|
||||||
value={line.billedAmount === 0 ? "" : line.billedAmount}
|
value={
|
||||||
|
line.totalBilled?.toNumber() === 0
|
||||||
|
? ""
|
||||||
|
: line.totalBilled?.toNumber()
|
||||||
|
}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateServiceLine(i, "billedAmount", e.target.value);
|
updateServiceLine(i, "totalBilled", e.target.value);
|
||||||
}}
|
}}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const val = parseFloat(e.target.value);
|
const val = parseFloat(e.target.value);
|
||||||
const rounded = Math.round(val * 100) / 100;
|
const rounded = Math.round(val * 100) / 100;
|
||||||
updateServiceLine(
|
updateServiceLine(
|
||||||
i,
|
i,
|
||||||
"billedAmount",
|
"totalBilled",
|
||||||
isNaN(rounded) ? 0 : rounded
|
isNaN(rounded) ? 0 : rounded
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -702,7 +661,9 @@ export function ClaimForm({
|
|||||||
oralCavityArea: "",
|
oralCavityArea: "",
|
||||||
toothNumber: "",
|
toothNumber: "",
|
||||||
toothSurface: "",
|
toothSurface: "",
|
||||||
billedAmount: 0,
|
totalBilled: new Decimal(0),
|
||||||
|
totalAdjusted: new Decimal(0),
|
||||||
|
totalPaid: new Decimal(0),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -6,31 +6,9 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
|
||||||
ClaimUncheckedCreateInputObjectSchema,
|
|
||||||
StaffUncheckedCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { z } from "zod";
|
|
||||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||||
|
import { ClaimWithServiceLines } from "@repo/db/types";
|
||||||
//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;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ClaimViewModalProps = {
|
type ClaimViewModalProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -205,14 +183,17 @@ export default function ClaimViewModal({
|
|||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Billed Amount:</span>{" "}
|
<span className="text-gray-500">Billed Amount:</span>{" "}
|
||||||
${line.billedAmount.toFixed(2)}
|
${line.totalBilled.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
||||||
Total Billed Amount: $
|
Total Billed Amount: $
|
||||||
{claim.serviceLines
|
{claim.serviceLines
|
||||||
.reduce((total, line) => total + line.billedAmount, 0)
|
.reduce(
|
||||||
|
(total, line) => total + line.totalBilled?.toNumber(),
|
||||||
|
0
|
||||||
|
)
|
||||||
.toFixed(2)}
|
.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import ClaimsRecentTable from "./claims-recent-table";
|
import ClaimsRecentTable from "./claims-recent-table";
|
||||||
import { PatientTable } from "../patients/patient-table";
|
import { PatientTable } from "../patients/patient-table";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -10,19 +8,15 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../ui/card";
|
} from "../ui/card";
|
||||||
|
import { Patient } from "@repo/db/types";
|
||||||
const PatientSchema = (
|
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).omit({
|
|
||||||
appointments: true,
|
|
||||||
});
|
|
||||||
type Patient = z.infer<typeof PatientSchema>;
|
|
||||||
|
|
||||||
interface ClaimsOfPatientModalProps {
|
interface ClaimsOfPatientModalProps {
|
||||||
onNewClaim?: (patientId: number) => void;
|
onNewClaim?: (patientId: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ClaimsOfPatientModal({ onNewClaim }: ClaimsOfPatientModalProps) {
|
export default function ClaimsOfPatientModal({
|
||||||
|
onNewClaim,
|
||||||
|
}: ClaimsOfPatientModalProps) {
|
||||||
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [claimsPage, setClaimsPage] = useState(1);
|
const [claimsPage, setClaimsPage] = useState(1);
|
||||||
|
|||||||
@@ -28,45 +28,13 @@ import {
|
|||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination";
|
} from "@/components/ui/pagination";
|
||||||
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
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 LoadingScreen from "@/components/ui/LoadingScreen";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||||
import ClaimViewModal from "./claim-view-modal";
|
import ClaimViewModal from "./claim-view-modal";
|
||||||
import ClaimEditModal from "./claim-edit-modal";
|
import ClaimEditModal from "./claim-edit-modal";
|
||||||
|
import { Claim, ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
|
||||||
//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>;
|
|
||||||
|
|
||||||
interface ClaimApiResponse {
|
interface ClaimApiResponse {
|
||||||
claims: ClaimWithServiceLines[];
|
claims: ClaimWithServiceLines[];
|
||||||
@@ -314,7 +282,7 @@ export default function ClaimsRecentTable({
|
|||||||
|
|
||||||
const getTotalBilled = (claim: ClaimWithServiceLines) => {
|
const getTotalBilled = (claim: ClaimWithServiceLines) => {
|
||||||
return claim.serviceLines.reduce(
|
return claim.serviceLines.reduce(
|
||||||
(sum, line) => sum + (line.billedAmount || 0),
|
(sum, line) => sum + (line.totalBilled?.toNumber() || 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useRef, useCallback } from 'react';
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { Upload, File, X, FilePlus } from 'lucide-react';
|
import { Upload, File, X, FilePlus } from "lucide-react";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface FileUploadZoneProps {
|
interface FileUploadZoneProps {
|
||||||
onFileUpload: (file: File) => void;
|
onFileUpload: (file: File) => void;
|
||||||
@@ -13,7 +13,7 @@ interface FileUploadZoneProps {
|
|||||||
export function FileUploadZone({
|
export function FileUploadZone({
|
||||||
onFileUpload,
|
onFileUpload,
|
||||||
isUploading,
|
isUploading,
|
||||||
acceptedFileTypes = "application/pdf"
|
acceptedFileTypes = "application/pdf",
|
||||||
}: FileUploadZoneProps) {
|
}: FileUploadZoneProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@@ -32,13 +32,16 @@ export function FileUploadZone({
|
|||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragOver = useCallback(
|
||||||
|
(e: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!isDragging) {
|
if (!isDragging) {
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
}
|
}
|
||||||
}, [isDragging]);
|
},
|
||||||
|
[isDragging]
|
||||||
|
);
|
||||||
|
|
||||||
const validateFile = (file: File) => {
|
const validateFile = (file: File) => {
|
||||||
// Check file type
|
// Check file type
|
||||||
@@ -46,7 +49,7 @@ export function FileUploadZone({
|
|||||||
toast({
|
toast({
|
||||||
title: "Invalid file type",
|
title: "Invalid file type",
|
||||||
description: "Please upload a PDF file.",
|
description: "Please upload a PDF file.",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,7 @@ export function FileUploadZone({
|
|||||||
toast({
|
toast({
|
||||||
title: "File too large",
|
title: "File too large",
|
||||||
description: "File size should be less than 5MB.",
|
description: "File size should be less than 5MB.",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -64,7 +67,8 @@ export function FileUploadZone({
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDrop = useCallback(
|
||||||
|
(e: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
@@ -77,9 +81,12 @@ export function FileUploadZone({
|
|||||||
onFileUpload(file);
|
onFileUpload(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [onFileUpload, acceptedFileTypes, toast]);
|
},
|
||||||
|
[onFileUpload, acceptedFileTypes, toast]
|
||||||
|
);
|
||||||
|
|
||||||
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileSelect = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (e.target.files && e.target.files[0]) {
|
if (e.target.files && e.target.files[0]) {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
|
|
||||||
@@ -88,7 +95,9 @@ export function FileUploadZone({
|
|||||||
onFileUpload(file);
|
onFileUpload(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [onFileUpload, acceptedFileTypes, toast]);
|
},
|
||||||
|
[onFileUpload, acceptedFileTypes, toast]
|
||||||
|
);
|
||||||
|
|
||||||
const handleBrowseClick = () => {
|
const handleBrowseClick = () => {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@@ -113,7 +122,9 @@ export function FileUploadZone({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-2 border-dashed rounded-lg p-8 flex flex-col items-center justify-center text-center transition-colors",
|
"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",
|
uploadedFile ? "bg-success/5" : "hover:bg-muted/40",
|
||||||
isUploading && "opacity-50 cursor-not-allowed"
|
isUploading && "opacity-50 cursor-not-allowed"
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function CredentialsModal({
|
|||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
providerName,
|
providerName,
|
||||||
isLoading = false
|
isLoading = false,
|
||||||
}: CredentialsModalProps) {
|
}: CredentialsModalProps) {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -51,7 +51,8 @@ export function CredentialsModal({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Insurance Portal Login</DialogTitle>
|
<DialogTitle>Insurance Portal Login</DialogTitle>
|
||||||
<DialogDescription>
|
<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>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { Link, useLocation } from "wouter";
|
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";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -62,7 +71,16 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="p-4 border-b border-gray-200 flex items-center space-x-2">
|
<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="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="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" />
|
<path d="M12 19c-5 0-8-2-9-5.5m18 0c-1 3.5-4 5.5-9 5.5Z" />
|
||||||
@@ -71,20 +89,18 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<div key={item.path}>
|
<div key={item.path}>
|
||||||
<Link
|
<Link to={item.path} onClick={() => setIsMobileOpen(false)}>
|
||||||
to={item.path}
|
<div
|
||||||
onClick={() => setIsMobileOpen(false)}
|
className={cn(
|
||||||
>
|
|
||||||
<div className={cn(
|
|
||||||
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
|
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
|
||||||
location === item.path
|
location === item.path
|
||||||
? "text-primary font-medium border-l-2 border-primary"
|
? "text-primary font-medium border-l-2 border-primary"
|
||||||
: "text-gray-600 hover:bg-gray-100"
|
: "text-gray-600 hover:bg-gray-100"
|
||||||
)}>
|
)}
|
||||||
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Bell, Menu} from "lucide-react";
|
import { Bell, Menu } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface TopAppBarProps {
|
interface TopAppBarProps {
|
||||||
toggleMobileMenu: () => void;
|
toggleMobileMenu: () => void;
|
||||||
}
|
}
|
||||||
@@ -41,7 +39,9 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
|||||||
>
|
>
|
||||||
<Menu className="h-5 w-5" />
|
<Menu className="h-5 w-5" />
|
||||||
</Button>
|
</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>
|
||||||
|
|
||||||
<div className="hidden md:flex md:flex-1 items-center justify-center">
|
<div className="hidden md:flex md:flex-1 items-center justify-center">
|
||||||
@@ -60,7 +60,10 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
|||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<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">
|
<Avatar className="h-8 w-8">
|
||||||
<AvatarImage src="" alt={user?.username} />
|
<AvatarImage src="" alt={user?.username} />
|
||||||
<AvatarFallback className="bg-primary text-white">
|
<AvatarFallback className="bg-primary text-white">
|
||||||
@@ -73,7 +76,8 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
|||||||
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
||||||
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
||||||
Account Settings</DropdownMenuItem>
|
Account Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={handleLogout}>
|
<DropdownMenuItem onClick={handleLogout}>
|
||||||
Log out
|
Log out
|
||||||
|
|||||||
@@ -15,38 +15,9 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { PatientForm, PatientFormRef } from "./patient-form";
|
import { PatientForm, PatientFormRef } from "./patient-form";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
|
||||||
import { X, Calendar } from "lucide-react";
|
import { X, Calendar } from "lucide-react";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
import { InsertPatient, Patient, UpdatePatient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface AddPatientModalProps {
|
interface AddPatientModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -112,8 +83,7 @@ export const AddPatientModal = forwardRef<
|
|||||||
if (patientFormRef.current) {
|
if (patientFormRef.current) {
|
||||||
patientFormRef.current.submit();
|
patientFormRef.current.submit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -32,34 +30,14 @@ import { format } from "date-fns";
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||||
|
import {
|
||||||
const PatientSchema = (
|
InsertPatient,
|
||||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
insertPatientSchema,
|
||||||
).omit({
|
Patient,
|
||||||
appointments: true,
|
UpdatePatient,
|
||||||
});
|
updatePatientSchema,
|
||||||
type Patient = z.infer<typeof PatientSchema>;
|
} from "@repo/db/types";
|
||||||
|
import { z } from "zod";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface PatientFormProps {
|
interface PatientFormProps {
|
||||||
patient?: Patient;
|
patient?: Patient;
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import {
|
|||||||
PaginationNext,
|
PaginationNext,
|
||||||
PaginationPrevious,
|
PaginationPrevious,
|
||||||
} from "@/components/ui/pagination";
|
} from "@/components/ui/pagination";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import LoadingScreen from "../ui/LoadingScreen";
|
import LoadingScreen from "../ui/LoadingScreen";
|
||||||
@@ -39,25 +37,7 @@ import { useDebounce } from "use-debounce";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Checkbox } from "../ui/checkbox";
|
import { Checkbox } from "../ui/checkbox";
|
||||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||||
|
import { Patient, UpdatePatient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
interface PatientApiResponse {
|
interface PatientApiResponse {
|
||||||
patients: Patient[];
|
patients: Patient[];
|
||||||
@@ -114,7 +94,7 @@ export function PatientTable({
|
|||||||
const handleSelectPatient = (patient: Patient) => {
|
const handleSelectPatient = (patient: Patient) => {
|
||||||
const isSelected = selectedPatientId === patient.id;
|
const isSelected = selectedPatientId === patient.id;
|
||||||
const newSelectedId = isSelected ? null : patient.id;
|
const newSelectedId = isSelected ? null : patient.id;
|
||||||
setSelectedPatientId(newSelectedId);
|
setSelectedPatientId(Number(newSelectedId));
|
||||||
|
|
||||||
if (onSelectPatient) {
|
if (onSelectPatient) {
|
||||||
onSelectPatient(isSelected ? null : patient);
|
onSelectPatient(isSelected ? null : patient);
|
||||||
@@ -258,7 +238,7 @@ export function PatientTable({
|
|||||||
if (currentPatient && user) {
|
if (currentPatient && user) {
|
||||||
const { id, ...sanitizedPatient } = patient;
|
const { id, ...sanitizedPatient } = patient;
|
||||||
updatePatientMutation.mutate({
|
updatePatientMutation.mutate({
|
||||||
id: currentPatient.id,
|
id: Number(currentPatient.id),
|
||||||
patient: sanitizedPatient,
|
patient: sanitizedPatient,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -288,7 +268,7 @@ export function PatientTable({
|
|||||||
|
|
||||||
const handleConfirmDeletePatient = async () => {
|
const handleConfirmDeletePatient = async () => {
|
||||||
if (currentPatient) {
|
if (currentPatient) {
|
||||||
deletePatientMutation.mutate(currentPatient.id);
|
deletePatientMutation.mutate(Number(currentPatient.id));
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
@@ -424,7 +404,7 @@ export function PatientTable({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
className={`h-10 w-10 ${getAvatarColor(patient.id)}`}
|
className={`h-10 w-10 ${getAvatarColor(Number(patient.id))}`}
|
||||||
>
|
>
|
||||||
<AvatarFallback className="text-white">
|
<AvatarFallback className="text-white">
|
||||||
{getInitials(patient.firstName, patient.lastName)}
|
{getInitials(patient.firstName, patient.lastName)}
|
||||||
@@ -436,7 +416,7 @@ export function PatientTable({
|
|||||||
{patient.firstName} {patient.lastName}
|
{patient.firstName} {patient.lastName}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
PID-{patient.id.toString().padStart(4, "0")}
|
PID-{patient.id?.toString().padStart(4, "0")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -508,7 +488,7 @@ export function PatientTable({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => onNewClaim?.(patient.id)}
|
onClick={() => onNewClaim?.(Number(patient.id))}
|
||||||
className="text-green-600 hover:text-green-800 hover:bg-green-50"
|
className="text-green-600 hover:text-green-800 hover:bg-green-50"
|
||||||
aria-label="New Claim"
|
aria-label="New Claim"
|
||||||
>
|
>
|
||||||
@@ -558,7 +538,7 @@ export function PatientTable({
|
|||||||
{currentPatient.firstName} {currentPatient.lastName}
|
{currentPatient.firstName} {currentPatient.lastName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500">
|
||||||
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
|
Patient ID: {currentPatient.id?.toString().padStart(4, "0")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -587,8 +567,10 @@ export function PatientTable({
|
|||||||
: "text-red-600"
|
: "text-red-600"
|
||||||
} font-medium`}
|
} font-medium`}
|
||||||
>
|
>
|
||||||
{currentPatient.status.charAt(0).toUpperCase() +
|
{currentPatient.status
|
||||||
currentPatient.status.slice(1)}
|
? currentPatient.status.charAt(0).toUpperCase() +
|
||||||
|
currentPatient.status.slice(1)
|
||||||
|
: "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -706,7 +688,7 @@ export function PatientTable({
|
|||||||
isOpen={isDeletePatientOpen}
|
isOpen={isDeletePatientOpen}
|
||||||
onConfirm={handleConfirmDeletePatient}
|
onConfirm={handleConfirmDeletePatient}
|
||||||
onCancel={() => setIsDeletePatientOpen(false)}
|
onCancel={() => setIsDeletePatientOpen(false)}
|
||||||
entityName={currentPatient?.name}
|
entityName={currentPatient?.firstName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
|
|||||||
@@ -6,11 +6,20 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
import {
|
||||||
|
formatDateToHumanReadable,
|
||||||
|
formatLocalDate,
|
||||||
|
parseLocalDate,
|
||||||
|
} from "@/utils/dateUtils";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { paymentStatusOptions, PaymentWithExtras } from "@repo/db/types";
|
import {
|
||||||
import { PaymentStatus, PaymentMethod } from "@repo/db/types";
|
PaymentStatus,
|
||||||
|
paymentStatusOptions,
|
||||||
|
PaymentMethod,
|
||||||
|
paymentMethodOptions,
|
||||||
|
PaymentWithExtras,
|
||||||
|
NewTransactionPayload,
|
||||||
|
} from "@repo/db/types";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -26,7 +35,7 @@ type PaymentEditModalProps = {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onEditServiceLine: (updatedPayment: PaymentWithExtras) => void;
|
onEditServiceLine: (payload: NewTransactionPayload) => void;
|
||||||
payment: PaymentWithExtras | null;
|
payment: PaymentWithExtras | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,143 +49,88 @@ export default function PaymentEditModal({
|
|||||||
if (!payment) return null;
|
if (!payment) return null;
|
||||||
|
|
||||||
const [expandedLineId, setExpandedLineId] = useState<number | null>(null);
|
const [expandedLineId, setExpandedLineId] = useState<number | null>(null);
|
||||||
const [updatedPaidAmounts, setUpdatedPaidAmounts] = useState<
|
const [paymentStatus, setPaymentStatus] = React.useState<PaymentStatus>(
|
||||||
Record<number, number>
|
payment.status
|
||||||
>({});
|
|
||||||
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 [formState, setFormState] = useState(() => {
|
||||||
type DraftPaymentData = {
|
return {
|
||||||
paidAmount: number;
|
serviceLineId: 0,
|
||||||
adjustedAmount?: number;
|
transactionId: "",
|
||||||
notes?: string;
|
paidAmount: 0,
|
||||||
paymentStatus?: PaymentStatus;
|
adjustedAmount: 0,
|
||||||
payerName?: string;
|
method: paymentMethodOptions[1] as PaymentMethod,
|
||||||
method: PaymentMethod;
|
receivedDate: formatLocalDate(new Date()),
|
||||||
receivedDate: string;
|
payerName: "",
|
||||||
|
notes: "",
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
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 handleEditServiceLine = (lineId: number) => {
|
const handleEditServiceLine = (lineId: number) => {
|
||||||
setExpandedLineId(lineId === expandedLineId ? null : lineId);
|
if (expandedLineId === lineId) {
|
||||||
};
|
// Closing current line
|
||||||
|
setExpandedLineId(null);
|
||||||
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")
|
|
||||||
return;
|
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,
|
paymentId: payment.id,
|
||||||
amount: data.paidAmount + (data.adjustedAmount ?? 0),
|
status: paymentStatus,
|
||||||
method: data.method,
|
serviceLineTransactions: [
|
||||||
payerName: data.payerName,
|
|
||||||
notes: data.notes,
|
|
||||||
receivedDate: new Date(data.receivedDate),
|
|
||||||
serviceLinePayments: [
|
|
||||||
{
|
{
|
||||||
serviceLineId: lineId,
|
serviceLineId: formState.serviceLineId,
|
||||||
paidAmount: data.paidAmount,
|
transactionId: formState.transactionId || undefined,
|
||||||
adjustedAmount: data.adjustedAmount ?? 0,
|
paidAmount: Number(formState.paidAmount),
|
||||||
notes: data.notes,
|
adjustedAmount: Number(formState.adjustedAmount) || 0,
|
||||||
|
method: formState.method,
|
||||||
|
receivedDate: parseLocalDate(formState.receivedDate),
|
||||||
|
payerName: formState.payerName || undefined,
|
||||||
|
notes: formState.notes || undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await onEditServiceLine(transactionPayload);
|
await onEditServiceLine(payload);
|
||||||
setExpandedLineId(null);
|
setExpandedLineId(null);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} 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 (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||||
@@ -200,6 +154,10 @@ export default function PaymentEditModal({
|
|||||||
Service Date:{" "}
|
Service Date:{" "}
|
||||||
{formatDateToHumanReadable(payment.claim.serviceDate)}
|
{formatDateToHumanReadable(payment.claim.serviceDate)}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Notes:</span>{" "}
|
||||||
|
{payment.notes || "N/A"}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment Summary */}
|
{/* Payment Summary */}
|
||||||
@@ -209,22 +167,22 @@ export default function PaymentEditModal({
|
|||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Total Billed:</span> $
|
<span className="text-gray-500">Total Billed:</span> $
|
||||||
{totalBilled.toFixed(2)}
|
{payment.totalBilled.toNumber().toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Total Paid:</span> $
|
<span className="text-gray-500">Total Paid:</span> $
|
||||||
{totalPaid.toFixed(2)}
|
{payment.totalPaid.toNumber().toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Total Due:</span> $
|
<span className="text-gray-500">Total Due:</span> $
|
||||||
{totalDue.toFixed(2)}
|
{payment.totalDue.toNumber().toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<label className="text-sm text-gray-600">Status</label>
|
<label className="text-sm text-gray-600">Status</label>
|
||||||
<Select
|
<Select
|
||||||
value={updatedPaymentStatus}
|
value={paymentStatus}
|
||||||
onValueChange={(value: PaymentStatus) =>
|
onValueChange={(value: PaymentStatus) =>
|
||||||
setUpdatedPaymentStatus(value)
|
setPaymentStatus(value)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
@@ -246,18 +204,16 @@ export default function PaymentEditModal({
|
|||||||
<h4 className="font-medium text-gray-900">Metadata</h4>
|
<h4 className="font-medium text-gray-900">Metadata</h4>
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Received Date:</span>{" "}
|
<span className="text-gray-500">Created At:</span>{" "}
|
||||||
{payment.receivedDate
|
{payment.createdAt
|
||||||
? formatDateToHumanReadable(payment.receivedDate)
|
? formatDateToHumanReadable(payment.createdAt)
|
||||||
: "N/A"}
|
: "N/A"}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Method:</span>{" "}
|
<span className="text-gray-500">Last Upadated At:</span>{" "}
|
||||||
{payment.paymentMethod ?? "N/A"}
|
{payment.updatedAt
|
||||||
</p>
|
? formatDateToHumanReadable(payment.updatedAt)
|
||||||
<p>
|
: "N/A"}
|
||||||
<span className="text-gray-500">Notes:</span>{" "}
|
|
||||||
{payment.notes || "N/A"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -270,22 +226,6 @@ export default function PaymentEditModal({
|
|||||||
{payment.claim.serviceLines.length > 0 ? (
|
{payment.claim.serviceLines.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{payment.claim.serviceLines.map((line) => {
|
{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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={line.id}
|
key={line.id}
|
||||||
@@ -297,19 +237,19 @@ export default function PaymentEditModal({
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Billed:</span> $
|
<span className="text-gray-500">Billed:</span> $
|
||||||
{line.billedAmount.toFixed(2)}
|
{line.totalBilled.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Paid:</span> $
|
<span className="text-gray-500">Paid:</span> $
|
||||||
{paidAmount.toFixed(2)}
|
{line.totalPaid.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Adjusted:</span> $
|
<span className="text-gray-500">Adjusted:</span> $
|
||||||
{adjusted.toFixed(2)}
|
{line.totalAdjusted.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Due:</span> $
|
<span className="text-gray-500">Due:</span> $
|
||||||
{due.toFixed(2)}
|
{line.totalDue.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
@@ -337,12 +277,12 @@ export default function PaymentEditModal({
|
|||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="Paid Amount"
|
placeholder="Paid Amount"
|
||||||
defaultValue={paidAmount}
|
defaultValue={formState.paidAmount}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setUpdatedPaidAmounts({
|
updateField(
|
||||||
...updatedPaidAmounts,
|
"paidAmount",
|
||||||
[line.id]: parseFloat(e.target.value),
|
parseFloat(e.target.value)
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -358,12 +298,65 @@ export default function PaymentEditModal({
|
|||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="Adjusted Amount"
|
placeholder="Adjusted Amount"
|
||||||
defaultValue={adjusted}
|
defaultValue={formState.adjustedAmount}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setUpdatedAdjustedAmounts({
|
updateField(
|
||||||
...updatedAdjustedAmounts,
|
"adjustedAmount",
|
||||||
[line.id]: parseFloat(e.target.value),
|
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>
|
</div>
|
||||||
@@ -380,16 +373,13 @@ export default function PaymentEditModal({
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Notes"
|
placeholder="Notes"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setUpdatedNotes({
|
updateField("notes", e.target.value)
|
||||||
...updatedNotes,
|
|
||||||
[line.id]: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleSavePayment(line.id)}
|
onClick={() => handleSavePayment()}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
@@ -409,8 +399,8 @@ export default function PaymentEditModal({
|
|||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium text-gray-900 pt-6">All Transactions</h4>
|
<h4 className="font-medium text-gray-900 pt-6">All Transactions</h4>
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
{payment.transactions.length > 0 ? (
|
{payment.serviceLineTransactions.length > 0 ? (
|
||||||
payment.transactions.map((tx) => (
|
payment.serviceLineTransactions.map((tx) => (
|
||||||
<div
|
<div
|
||||||
key={tx.id}
|
key={tx.id}
|
||||||
className="border p-3 rounded-md bg-white shadow-sm"
|
className="border p-3 rounded-md bg-white shadow-sm"
|
||||||
@@ -420,18 +410,27 @@ export default function PaymentEditModal({
|
|||||||
{formatDateToHumanReadable(tx.receivedDate)}
|
{formatDateToHumanReadable(tx.receivedDate)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Amount:</span> $
|
<span className="text-gray-500">Paid Amount:</span> $
|
||||||
{Number(tx.amount).toFixed(2)}
|
{Number(tx.paidAmount).toFixed(2)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Adjusted Amount:</span> $
|
||||||
|
{Number(tx.adjustedAmount).toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">Method:</span> {tx.method}
|
<span className="text-gray-500">Method:</span> {tx.method}
|
||||||
</p>
|
</p>
|
||||||
{tx.serviceLinePayments.map((sp) => (
|
{tx.payerName && (
|
||||||
<p key={sp.id} className="text-sm text-gray-600 ml-2">
|
<p>
|
||||||
• Applied ${Number(sp.paidAmount).toFixed(2)} to service
|
<span className="text-gray-500">Payer Name:</span>{" "}
|
||||||
line ID {sp.serviceLineId}
|
{tx.payerName}
|
||||||
</p>
|
</p>
|
||||||
))}
|
)}
|
||||||
|
{tx.notes && (
|
||||||
|
<p>
|
||||||
|
<span className="text-gray-500">Notes:</span> {tx.notes}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -31,12 +31,11 @@ import {
|
|||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
||||||
import PaymentViewModal from "./payment-view-modal";
|
import PaymentViewModal from "./payment-view-modal";
|
||||||
import PaymentEditModal from "./payment-edit-modal";
|
|
||||||
import LoadingScreen from "../ui/LoadingScreen";
|
import LoadingScreen from "../ui/LoadingScreen";
|
||||||
import {
|
import {
|
||||||
ClaimStatus,
|
ClaimStatus,
|
||||||
ClaimWithServiceLines,
|
ClaimWithServiceLines,
|
||||||
Payment,
|
NewTransactionPayload,
|
||||||
PaymentWithExtras,
|
PaymentWithExtras,
|
||||||
} from "@repo/db/types";
|
} from "@repo/db/types";
|
||||||
import EditPaymentModal from "./payment-edit-modal";
|
import EditPaymentModal from "./payment-edit-modal";
|
||||||
@@ -118,10 +117,14 @@ export default function PaymentsRecentTable({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updatePaymentMutation = useMutation({
|
const updatePaymentMutation = useMutation({
|
||||||
mutationFn: async (payment: PaymentWithExtras) => {
|
mutationFn: async (data: NewTransactionPayload) => {
|
||||||
const response = await apiRequest("PUT", `/api/claims/${payment.id}`, {
|
const response = await apiRequest(
|
||||||
data: payment,
|
"PUT",
|
||||||
});
|
`/api/claims/${data.paymentId}`,
|
||||||
|
{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
throw new Error(error.message || "Failed to update Payment");
|
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 | "...")[] {
|
function getPageNumbers(current: number, total: number): (number | "...")[] {
|
||||||
const delta = 2;
|
const delta = 2;
|
||||||
const range: (number | "...")[] = [];
|
const range: (number | "...")[] = [];
|
||||||
@@ -359,19 +355,9 @@ export default function PaymentsRecentTable({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
) : (
|
) : (
|
||||||
paymentsData?.payments.map((payment) => {
|
paymentsData?.payments.map((payment) => {
|
||||||
const claim = (payment as PaymentWithExtras)
|
const totalBilled = payment.totalBilled.toNumber();
|
||||||
.claim as ClaimWithServiceLines;
|
const totalPaid = payment.totalPaid.toNumber();
|
||||||
|
const totalDue = payment.totalDue.toNumber();
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={payment.id}>
|
<TableRow key={payment.id}>
|
||||||
@@ -401,9 +387,9 @@ export default function PaymentsRecentTable({
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<strong>Total Due:</strong>{" "}
|
<strong>Total Due:</strong>{" "}
|
||||||
{outstanding > 0 ? (
|
{totalDue > 0 ? (
|
||||||
<span className="text-yellow-600">
|
<span className="text-yellow-600">
|
||||||
${outstanding.toFixed(2)}
|
${totalDue.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-green-600">Settled</span>
|
<span className="text-green-600">Settled</span>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
|
import { Staff } from "@repo/db/types";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
interface StaffFormProps {
|
interface StaffFormProps {
|
||||||
initialData?: Partial<Staff>;
|
initialData?: Partial<Staff>;
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { z } from "zod";
|
|
||||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Delete, Edit } from "lucide-react";
|
import { Delete, Edit } from "lucide-react";
|
||||||
|
import { Staff } from "@repo/db/types";
|
||||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
|
|
||||||
const staffUpdateSchema = (
|
|
||||||
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
|
||||||
).partial();
|
|
||||||
|
|
||||||
interface StaffTableProps {
|
interface StaffTableProps {
|
||||||
staff: Staff[];
|
staff: Staff[];
|
||||||
@@ -21,7 +13,6 @@ interface StaffTableProps {
|
|||||||
onView: (staff: Staff) => void;
|
onView: (staff: Staff) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function StaffTable({
|
export function StaffTable({
|
||||||
staff,
|
staff,
|
||||||
onEdit,
|
onEdit,
|
||||||
@@ -151,15 +142,13 @@ export function StaffTable({
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() => staff !== undefined && onDelete(staff)}
|
||||||
staff !== undefined && onDelete(staff)
|
|
||||||
}
|
|
||||||
className="text-red-600 hover:text-red-900"
|
className="text-red-600 hover:text-red-900"
|
||||||
aria-label="Delete Staff"
|
aria-label="Delete Staff"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
>
|
>
|
||||||
<Delete/>
|
<Delete />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => staff.id !== undefined && onEdit(staff)}
|
onClick={() => staff.id !== undefined && onEdit(staff)}
|
||||||
@@ -170,7 +159,6 @@ export function StaffTable({
|
|||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@@ -235,7 +223,9 @@ export function StaffTable({
|
|||||||
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
|
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 ${
|
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
|
Next
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { format, addDays, startOfToday, addMinutes } from "date-fns";
|
import { format, addDays, startOfToday, addMinutes } from "date-fns";
|
||||||
import {
|
import { parseLocalDate, formatLocalDate } from "@/utils/dateUtils";
|
||||||
parseLocalDate,
|
|
||||||
formatLocalDate,
|
|
||||||
} from "@/utils/dateUtils";
|
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
|
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
|
||||||
@@ -19,12 +16,7 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { z } from "zod";
|
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import {
|
|
||||||
AppointmentUncheckedCreateInputObjectSchema,
|
|
||||||
PatientUncheckedCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import {
|
import {
|
||||||
@@ -40,35 +32,12 @@ import { Menu, Item, useContextMenu } from "react-contexify";
|
|||||||
import "react-contexify/ReactContexify.css";
|
import "react-contexify/ReactContexify.css";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
|
import {
|
||||||
//creating types out of schema auto generated.
|
Appointment,
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
InsertAppointment,
|
||||||
|
Patient,
|
||||||
const insertAppointmentSchema = (
|
UpdateAppointment,
|
||||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
} from "@repo/db/types";
|
||||||
).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>;
|
|
||||||
|
|
||||||
// Define types for scheduling
|
// Define types for scheduling
|
||||||
interface TimeSlot {
|
interface TimeSlot {
|
||||||
@@ -287,7 +256,11 @@ export default function AppointmentsPage() {
|
|||||||
// Create/upsert appointment mutation
|
// Create/upsert appointment mutation
|
||||||
const createAppointmentMutation = useMutation({
|
const createAppointmentMutation = useMutation({
|
||||||
mutationFn: async (appointment: InsertAppointment) => {
|
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();
|
return await res.json();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -436,7 +409,7 @@ export default function AppointmentsPage() {
|
|||||||
const formattedDate = format(selectedDate, "yyyy-MM-dd");
|
const formattedDate = format(selectedDate, "yyyy-MM-dd");
|
||||||
|
|
||||||
const selectedDateAppointments = appointments.filter((appointment) => {
|
const selectedDateAppointments = appointments.filter((appointment) => {
|
||||||
const dateObj = parseLocalDate(appointment.date)
|
const dateObj = parseLocalDate(appointment.date);
|
||||||
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
|
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -467,7 +440,7 @@ export default function AppointmentsPage() {
|
|||||||
patientName,
|
patientName,
|
||||||
staffId,
|
staffId,
|
||||||
status: apt.status ?? null,
|
status: apt.status ?? null,
|
||||||
date: formatLocalDate(parseLocalDate(apt.date))
|
date: formatLocalDate(parseLocalDate(apt.date)),
|
||||||
};
|
};
|
||||||
|
|
||||||
return processed;
|
return processed;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
|
||||||
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -16,41 +14,17 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
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 { CheckCircle, Torus } from "lucide-react";
|
||||||
import { CheckedState } from "@radix-ui/react-checkbox";
|
import { CheckedState } from "@radix-ui/react-checkbox";
|
||||||
import LoadingScreen from "@/components/ui/LoadingScreen";
|
import LoadingScreen from "@/components/ui/LoadingScreen";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
import {
|
||||||
const insertUserSchema = (
|
LoginFormValues,
|
||||||
UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
loginSchema,
|
||||||
).pick({
|
RegisterFormValues,
|
||||||
username: true,
|
registerSchema,
|
||||||
password: true,
|
} from "@repo/db/types";
|
||||||
});
|
|
||||||
|
|
||||||
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>;
|
|
||||||
|
|
||||||
export default function AuthPage() {
|
export default function AuthPage() {
|
||||||
const [activeTab, setActiveTab] = useState<string>("login");
|
const [activeTab, setActiveTab] = useState<string>("login");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useMemo } from "react";
|
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 { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import {
|
import {
|
||||||
@@ -12,13 +12,7 @@ import {
|
|||||||
import { ClaimForm } from "@/components/claims/claim-form";
|
import { ClaimForm } from "@/components/claims/claim-form";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import {
|
|
||||||
PatientUncheckedCreateInputObjectSchema,
|
|
||||||
AppointmentUncheckedCreateInputObjectSchema,
|
|
||||||
ClaimUncheckedCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import { parse } from "date-fns";
|
import { parse } from "date-fns";
|
||||||
import { z } from "zod";
|
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||||
@@ -27,58 +21,16 @@ import {
|
|||||||
clearTaskStatus,
|
clearTaskStatus,
|
||||||
} from "@/redux/slices/seleniumClaimSubmitTaskSlice";
|
} from "@/redux/slices/seleniumClaimSubmitTaskSlice";
|
||||||
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
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 ClaimsRecentTable from "@/components/claims/claims-recent-table";
|
||||||
import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table";
|
import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table";
|
||||||
|
import {
|
||||||
//creating types out of schema auto generated.
|
Claim,
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
InsertAppointment,
|
||||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
InsertPatient,
|
||||||
|
UpdateAppointment,
|
||||||
const insertAppointmentSchema = (
|
UpdatePatient,
|
||||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
} from "@repo/db/types";
|
||||||
).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>;
|
|
||||||
|
|
||||||
export default function ClaimsPage() {
|
export default function ClaimsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
|||||||
@@ -27,42 +27,13 @@ import {
|
|||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||||
|
import {
|
||||||
//creating types out of schema auto generated.
|
Appointment,
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
InsertAppointment,
|
||||||
|
InsertPatient,
|
||||||
const insertAppointmentSchema = (
|
Patient,
|
||||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
UpdateAppointment,
|
||||||
).omit({
|
} from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
// Type for the ref to access modal methods
|
// Type for the ref to access modal methods
|
||||||
type AddPatientModalRef = {
|
type AddPatientModalRef = {
|
||||||
@@ -156,7 +127,11 @@ export default function Dashboard() {
|
|||||||
// Create/upsert appointment mutation
|
// Create/upsert appointment mutation
|
||||||
const createAppointmentMutation = useMutation({
|
const createAppointmentMutation = useMutation({
|
||||||
mutationFn: async (appointment: InsertAppointment) => {
|
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();
|
return await res.json();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -11,26 +11,11 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { toast } from "@/hooks/use-toast";
|
import { toast } from "@/hooks/use-toast";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { Eye, Trash, Download, FolderOpen } from "lucide-react";
|
import { Eye, Trash, Download, FolderOpen } from "lucide-react";
|
||||||
import {
|
|
||||||
PatientUncheckedCreateInputObjectSchema,
|
|
||||||
PdfFileUncheckedCreateInputObjectSchema,
|
|
||||||
} from "@repo/db/usedSchemas";
|
|
||||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
import { PatientTable } from "@/components/patients/patient-table";
|
import { PatientTable } from "@/components/patients/patient-table";
|
||||||
import { z } from "zod";
|
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
|
import { Patient, PdfFile } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
export default function DocumentsPage() {
|
export default function DocumentsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
@@ -47,8 +32,7 @@ export default function DocumentsPage() {
|
|||||||
setSelectedGroupId(null);
|
setSelectedGroupId(null);
|
||||||
setFileBlobUrl(null);
|
setFileBlobUrl(null);
|
||||||
setSelectedPdfId(null);
|
setSelectedPdfId(null);
|
||||||
}, [selectedPatient]);
|
}, [selectedPatient]);
|
||||||
|
|
||||||
|
|
||||||
const { data: groups = [] } = useQuery({
|
const { data: groups = [] } = useQuery({
|
||||||
queryKey: ["groups", selectedPatient?.id],
|
queryKey: ["groups", selectedPatient?.id],
|
||||||
@@ -99,7 +83,7 @@ export default function DocumentsPage() {
|
|||||||
|
|
||||||
const handleConfirmDeletePdf = () => {
|
const handleConfirmDeletePdf = () => {
|
||||||
if (currentPdf) {
|
if (currentPdf) {
|
||||||
deletePdfMutation.mutate(currentPdf.id);
|
deletePdfMutation.mutate(Number(currentPdf.id));
|
||||||
} else {
|
} else {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ import {
|
|||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { CalendarIcon, CheckCircle, LoaderCircleIcon } from "lucide-react";
|
import { CalendarIcon, CheckCircle, LoaderCircleIcon } from "lucide-react";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { z } from "zod";
|
|
||||||
import { PatientTable } from "@/components/patients/patient-table";
|
import { PatientTable } from "@/components/patients/patient-table";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
@@ -34,22 +32,7 @@ import {
|
|||||||
} from "@/redux/slices/seleniumEligibilityCheckTaskSlice";
|
} from "@/redux/slices/seleniumEligibilityCheckTaskSlice";
|
||||||
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
||||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||||
|
import { InsertPatient, Patient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
export default function InsuranceEligibilityPage() {
|
export default function InsuranceEligibilityPage() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useMemo, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||||
import { Sidebar } from "@/components/layout/sidebar";
|
import { Sidebar } from "@/components/layout/sidebar";
|
||||||
import { PatientTable } from "@/components/patients/patient-table";
|
import { PatientTable } from "@/components/patients/patient-table";
|
||||||
@@ -15,28 +15,11 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { z } from "zod";
|
|
||||||
import useExtractPdfData from "@/hooks/use-extractPdfData";
|
import useExtractPdfData from "@/hooks/use-extractPdfData";
|
||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
|
import { InsertPatient, Patient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
// Type for the ref to access modal methods
|
// Type for the ref to access modal methods
|
||||||
type AddPatientModalRef = {
|
type AddPatientModalRef = {
|
||||||
@@ -105,7 +88,6 @@ export default function PatientsPage() {
|
|||||||
|
|
||||||
const isLoading = addPatientMutation.isPending;
|
const isLoading = addPatientMutation.isPending;
|
||||||
|
|
||||||
|
|
||||||
// File upload handling
|
// File upload handling
|
||||||
const handleFileUpload = (file: File) => {
|
const handleFileUpload = (file: File) => {
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
|
|||||||
@@ -13,15 +13,9 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import {
|
import {
|
||||||
CreditCard,
|
|
||||||
Clock,
|
|
||||||
CheckCircle,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
DollarSign,
|
DollarSign,
|
||||||
Receipt,
|
|
||||||
Plus,
|
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ReceiptText,
|
|
||||||
Upload,
|
Upload,
|
||||||
Image,
|
Image,
|
||||||
X,
|
X,
|
||||||
@@ -53,56 +47,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
|
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
|
||||||
|
import { Appointment, Patient } from "@repo/db/types";
|
||||||
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>;
|
|
||||||
|
|
||||||
export default function PaymentsPage() {
|
export default function PaymentsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
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 { Plus, ClipboardCheck, Clock, CheckCircle, AlertCircle } from "lucide-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
import { Appointment, Patient } from "@repo/db/types";
|
||||||
|
|
||||||
export default function PreAuthorizationsPage() {
|
export default function PreAuthorizationsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
@@ -138,7 +137,7 @@ export default function PreAuthorizationsPage() {
|
|||||||
key={patient.id}
|
key={patient.id}
|
||||||
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={() => {
|
||||||
setSelectedPatient(patient.id);
|
setSelectedPatient(Number(patient.id));
|
||||||
handleNewPreAuth(
|
handleNewPreAuth(
|
||||||
patient.id,
|
patient.id,
|
||||||
dentalProcedures[Math.floor(Math.random() * 3)].name
|
dentalProcedures[Math.floor(Math.random() * 3)].name
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ import { Sidebar } from "@/components/layout/sidebar";
|
|||||||
import { StaffTable } from "@/components/staffs/staff-table";
|
import { StaffTable } from "@/components/staffs/staff-table";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { StaffForm } from "@/components/staffs/staff-form";
|
import { StaffForm } from "@/components/staffs/staff-form";
|
||||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||||
import { CredentialTable } from "@/components/settings/insuranceCredTable";
|
import { CredentialTable } from "@/components/settings/insuranceCredTable";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
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() {
|
export default function SettingsPage() {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|||||||
1261
backup_before_totalBilled.sql
Normal file
1261
backup_before_totalBilled.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -135,7 +135,7 @@ model ServiceLine {
|
|||||||
oralCavityArea String?
|
oralCavityArea String?
|
||||||
toothNumber String?
|
toothNumber String?
|
||||||
toothSurface String?
|
toothSurface String?
|
||||||
billedAmount Float
|
totalBilled Decimal @db.Decimal(10, 2)
|
||||||
totalPaid Decimal @default(0.00) @db.Decimal(10, 2)
|
totalPaid Decimal @default(0.00) @db.Decimal(10, 2)
|
||||||
totalAdjusted Decimal @default(0.00) @db.Decimal(10, 2)
|
totalAdjusted Decimal @default(0.00) @db.Decimal(10, 2)
|
||||||
totalDue Decimal @default(0.00) @db.Decimal(10, 2)
|
totalDue Decimal @default(0.00) @db.Decimal(10, 2)
|
||||||
|
|||||||
22
packages/db/types/appointment-types.ts
Normal file
22
packages/db/types/appointment-types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { AppointmentUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||||
|
|
||||||
|
export const insertAppointmentSchema = (
|
||||||
|
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
});
|
||||||
|
export type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||||
|
|
||||||
|
export const updateAppointmentSchema = (
|
||||||
|
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
)
|
||||||
|
.omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
export type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||||
59
packages/db/types/claim-types.ts
Normal file
59
packages/db/types/claim-types.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { ClaimStatusSchema, ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
import { Decimal } from "decimal.js";
|
||||||
|
import { Staff } from "@repo/db/types";
|
||||||
|
|
||||||
|
export const insertClaimSchema = (
|
||||||
|
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
});
|
||||||
|
export type InsertClaim = z.infer<typeof insertClaimSchema>;
|
||||||
|
|
||||||
|
export const updateClaimSchema = (
|
||||||
|
ClaimUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
)
|
||||||
|
.omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
export type UpdateClaim = z.infer<typeof updateClaimSchema>;
|
||||||
|
|
||||||
|
export type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||||
|
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//used in claim-form
|
||||||
|
export interface InputServiceLine {
|
||||||
|
procedureCode: string;
|
||||||
|
procedureDate: string; // YYYY-MM-DD
|
||||||
|
oralCavityArea?: string;
|
||||||
|
toothNumber?: string;
|
||||||
|
toothSurface?: string;
|
||||||
|
totalBilled: Decimal;
|
||||||
|
totalPaid?: Decimal;
|
||||||
|
totalAdjusted?: Decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claim model with embedded service lines
|
||||||
|
export type ClaimWithServiceLines = Claim & {
|
||||||
|
serviceLines: {
|
||||||
|
id: number;
|
||||||
|
claimId: number;
|
||||||
|
procedureCode: string;
|
||||||
|
procedureDate: Date;
|
||||||
|
oralCavityArea: string | null;
|
||||||
|
toothNumber: string | null;
|
||||||
|
toothSurface: string | null;
|
||||||
|
totalBilled: Decimal;
|
||||||
|
totalPaid: Decimal;
|
||||||
|
totalAdjusted: Decimal;
|
||||||
|
status: string;
|
||||||
|
}[];
|
||||||
|
staff?: Staff | null;
|
||||||
|
};
|
||||||
@@ -1,2 +1,8 @@
|
|||||||
|
export * from "./appointment-types";
|
||||||
|
export * from "./claim-types";
|
||||||
|
export * from "./insurance-types";
|
||||||
|
export * from "./patient-types";;
|
||||||
export * from "./payment-types";
|
export * from "./payment-types";
|
||||||
|
export * from "./pdf-types";
|
||||||
|
export * from "./staff-types";
|
||||||
|
export * from "./user-types";
|
||||||
14
packages/db/types/insurance-types.ts
Normal file
14
packages/db/types/insurance-types.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { InsuranceCredentialUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type InsuranceCredential = z.infer<
|
||||||
|
typeof InsuranceCredentialUncheckedCreateInputObjectSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const insertInsuranceCredentialSchema = (
|
||||||
|
InsuranceCredentialUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).omit({ id: true });
|
||||||
|
|
||||||
|
export type InsertInsuranceCredential = z.infer<
|
||||||
|
typeof insertInsuranceCredentialSchema
|
||||||
|
>;
|
||||||
24
packages/db/types/patient-types.ts
Normal file
24
packages/db/types/patient-types.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type Patient = z.infer<typeof PatientUncheckedCreateInputObjectSchema>;
|
||||||
|
|
||||||
|
export const insertPatientSchema = (
|
||||||
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
});
|
||||||
|
export type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||||
|
|
||||||
|
export const updatePatientSchema = (
|
||||||
|
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
)
|
||||||
|
.omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
userId: true,
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
export type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||||
@@ -91,38 +91,18 @@ export type PaymentWithExtras = Prisma.PaymentGetPayload<{
|
|||||||
paymentMethod: string;
|
paymentMethod: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Claim model with embedded service lines
|
|
||||||
export type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
|
||||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
|
||||||
|
|
||||||
export type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
|
||||||
|
|
||||||
export type ClaimWithServiceLines = Claim & {
|
|
||||||
serviceLines: {
|
|
||||||
id: number;
|
|
||||||
claimId: number;
|
|
||||||
procedureCode: string;
|
|
||||||
procedureDate: Date;
|
|
||||||
oralCavityArea: string | null;
|
|
||||||
toothNumber: string | null;
|
|
||||||
toothSurface: string | null;
|
|
||||||
billedAmount: number;
|
|
||||||
status: string;
|
|
||||||
}[];
|
|
||||||
staff: Staff | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NewTransactionPayload = {
|
export type NewTransactionPayload = {
|
||||||
paymentId: number;
|
paymentId: number;
|
||||||
amount: number;
|
status: PaymentStatus;
|
||||||
method: PaymentMethod;
|
|
||||||
payerName?: string;
|
|
||||||
notes?: string;
|
|
||||||
receivedDate: Date;
|
|
||||||
serviceLineTransactions: {
|
serviceLineTransactions: {
|
||||||
serviceLineId: number;
|
serviceLineId: number;
|
||||||
|
transactionId?: string;
|
||||||
paidAmount: number;
|
paidAmount: number;
|
||||||
adjustedAmount: number;
|
adjustedAmount?: number;
|
||||||
|
method: PaymentMethod;
|
||||||
|
receivedDate: Date;
|
||||||
|
payerName?: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|||||||
12
packages/db/types/pdf-types.ts
Normal file
12
packages/db/types/pdf-types.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { PdfCategorySchema, PdfFileUncheckedCreateInputObjectSchema, PdfGroupUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type PdfGroup = z.infer<typeof PdfGroupUncheckedCreateInputObjectSchema>;
|
||||||
|
export type PdfFile = z.infer<typeof PdfFileUncheckedCreateInputObjectSchema>;
|
||||||
|
export type PdfCategory = z.infer<typeof PdfCategorySchema>;
|
||||||
|
|
||||||
|
export interface ClaimPdfMetadata {
|
||||||
|
id: number;
|
||||||
|
filename: string;
|
||||||
|
uploadedAt: Date;
|
||||||
|
}
|
||||||
4
packages/db/types/staff-types.ts
Normal file
4
packages/db/types/staff-types.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||||
36
packages/db/types/user-types.ts
Normal file
36
packages/db/types/user-types.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export type User = z.infer<typeof UserUncheckedCreateInputObjectSchema>;
|
||||||
|
|
||||||
|
export const insertUserSchema = (
|
||||||
|
UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||||
|
).pick({
|
||||||
|
username: true,
|
||||||
|
password: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
|
||||||
|
rememberMe: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export 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"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
||||||
|
export type LoginFormValues = z.infer<typeof loginSchema>;
|
||||||
|
export type RegisterFormValues = z.infer<typeof registerSchema>;
|
||||||
Reference in New Issue
Block a user