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 { storage } from "../storage";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
PaymentUncheckedCreateInputObjectSchema,
|
||||
PaymentTransactionCreateInputObjectSchema,
|
||||
ServiceLinePaymentCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { Prisma } from "@repo/db/generated/prisma";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
// 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>;
|
||||
import { insertPaymentSchema, updatePaymentSchema } from "@repo/db/types";
|
||||
|
||||
const paymentFilterSchema = z.object({
|
||||
from: z.string().datetime(),
|
||||
|
||||
@@ -1,165 +1,29 @@
|
||||
import { prisma as db } from "@repo/db/client";
|
||||
import { PdfCategory } from "@repo/db/generated/prisma";
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
UserUncheckedCreateInputObjectSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
InsuranceCredentialUncheckedCreateInputObjectSchema,
|
||||
PdfFileUncheckedCreateInputObjectSchema,
|
||||
PdfGroupUncheckedCreateInputObjectSchema,
|
||||
PdfCategorySchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Appointment,
|
||||
Claim,
|
||||
ClaimWithServiceLines,
|
||||
InsertAppointment,
|
||||
InsertClaim,
|
||||
InsertInsuranceCredential,
|
||||
InsertPatient,
|
||||
InsertPayment,
|
||||
InsertUser,
|
||||
InsuranceCredential,
|
||||
Patient,
|
||||
Payment,
|
||||
PaymentWithExtras,
|
||||
PdfFile,
|
||||
PdfGroup,
|
||||
Staff,
|
||||
UpdateAppointment,
|
||||
UpdateClaim,
|
||||
UpdatePatient,
|
||||
UpdatePayment,
|
||||
User,
|
||||
} 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 {
|
||||
// User methods
|
||||
getUser(id: number): Promise<User | undefined>;
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
import { useState } from "react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { AppointmentForm } from "./appointment-form";
|
||||
import { AppointmentUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
|
||||
import {z} from "zod";
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
}).partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
const PatientSchema = (PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
import {
|
||||
Appointment,
|
||||
InsertAppointment,
|
||||
Patient,
|
||||
UpdateAppointment,
|
||||
} from "@repo/db/types";
|
||||
|
||||
interface AddAppointmentModalProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (data: InsertAppointment | UpdateAppointment) => void;
|
||||
onDelete?: (id: number) => void;
|
||||
onDelete?: (id: number) => void;
|
||||
isLoading: boolean;
|
||||
appointment?: Appointment;
|
||||
patients: Patient[];
|
||||
@@ -42,7 +31,6 @@ export function AddAppointmentModal({
|
||||
appointment,
|
||||
patients,
|
||||
}: AddAppointmentModalProps) {
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
@@ -61,10 +49,10 @@ export function AddAppointmentModal({
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
onDelete={onDelete}
|
||||
onOpenChange={onOpenChange}
|
||||
onOpenChange={onOpenChange}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { format } from "date-fns";
|
||||
@@ -33,41 +33,13 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useDebounce } from "use-debounce";
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
|
||||
import { z } from "zod";
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
Appointment,
|
||||
InsertAppointment,
|
||||
insertAppointmentSchema,
|
||||
Patient,
|
||||
Staff,
|
||||
UpdateAppointment,
|
||||
} from "@repo/db/types";
|
||||
|
||||
interface AppointmentFormProps {
|
||||
appointment?: Appointment;
|
||||
@@ -211,7 +183,7 @@ export function AppointmentForm({
|
||||
const term = debouncedSearchTerm.toLowerCase();
|
||||
setFilteredPatients(
|
||||
patients.filter((p) =>
|
||||
`${p.firstName} ${p.lastName} ${p.phone} ${p.dob}`
|
||||
`${p.firstName} ${p.lastName} ${p.phone} ${p.dateOfBirth}`
|
||||
.toLowerCase()
|
||||
.includes(term)
|
||||
)
|
||||
@@ -351,7 +323,7 @@ export function AppointmentForm({
|
||||
filteredPatients.map((patient) => (
|
||||
<SelectItem
|
||||
key={patient.id}
|
||||
value={patient.id.toString()}
|
||||
value={patient.id?.toString() ?? ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">
|
||||
@@ -435,7 +407,7 @@ export function AppointmentForm({
|
||||
selected={field.value}
|
||||
onSelect={(date) => {
|
||||
if (date) {
|
||||
field.onChange(date);
|
||||
field.onChange(date);
|
||||
}
|
||||
}}
|
||||
disabled={(date: Date) =>
|
||||
|
||||
@@ -25,20 +25,7 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
|
||||
import { z } from "zod";
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
import { Appointment, Patient } from "@repo/db/types";
|
||||
|
||||
interface AppointmentTableProps {
|
||||
appointments: Appointment[];
|
||||
|
||||
@@ -15,29 +15,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
import React, { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { ClaimStatus } from "./claims-recent-table";
|
||||
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
type ClaimWithServiceLines = Claim & {
|
||||
serviceLines: {
|
||||
id: number;
|
||||
claimId: number;
|
||||
procedureCode: string;
|
||||
procedureDate: Date;
|
||||
oralCavityArea: string | null;
|
||||
toothNumber: string | null;
|
||||
toothSurface: string | null;
|
||||
billedAmount: number;
|
||||
}[];
|
||||
staff: Staff | null;
|
||||
};
|
||||
import { ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
|
||||
|
||||
type ClaimEditModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -223,14 +201,17 @@ export default function ClaimEditModal({
|
||||
)}
|
||||
<p>
|
||||
<span className="text-gray-500">Billed Amount:</span> $
|
||||
{line.billedAmount.toFixed(2)}
|
||||
{line.totalBilled.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
||||
Total Billed Amount: $
|
||||
{claim.serviceLines
|
||||
.reduce((total, line) => total + line.billedAmount, 0)
|
||||
.reduce(
|
||||
(total, line) => total + line.totalBilled?.toNumber(),
|
||||
0
|
||||
)
|
||||
.toFixed(2)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -18,14 +18,6 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
ClaimStatusSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import { MultipleFileUploadZone } from "../file-upload/multiple-file-upload-zone";
|
||||
@@ -37,57 +29,16 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import procedureCodes from "../../assets/data/procedureCodes.json";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
|
||||
interface ServiceLine {
|
||||
procedureCode: string;
|
||||
procedureDate: string; // YYYY-MM-DD
|
||||
oralCavityArea?: string;
|
||||
toothNumber?: string;
|
||||
toothSurface?: string;
|
||||
billedAmount: number;
|
||||
}
|
||||
import {
|
||||
Claim,
|
||||
InputServiceLine,
|
||||
InsertAppointment,
|
||||
Patient,
|
||||
Staff,
|
||||
UpdateAppointment,
|
||||
UpdatePatient,
|
||||
} from "@repo/db/types";
|
||||
import { Decimal } from "decimal.js";
|
||||
|
||||
interface ClaimFormData {
|
||||
patientId: number;
|
||||
@@ -102,7 +53,7 @@ interface ClaimFormData {
|
||||
insuranceProvider: string;
|
||||
insuranceSiteKey?: string;
|
||||
status: string; // default "pending"
|
||||
serviceLines: ServiceLine[];
|
||||
serviceLines: InputServiceLine[];
|
||||
claimId?: number;
|
||||
}
|
||||
|
||||
@@ -117,9 +68,6 @@ interface ClaimFormProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
export function ClaimForm({
|
||||
patientId,
|
||||
onHandleAppointmentSubmit,
|
||||
@@ -207,16 +155,21 @@ export function ClaimForm({
|
||||
}, [serviceDate]);
|
||||
|
||||
// Determine patient date of birth format - required as date extracted from pdfs has different format.
|
||||
const formatDOB = (dob: string | undefined) => {
|
||||
const formatDOB = (dob: string | Date | undefined) => {
|
||||
if (!dob) return "";
|
||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(dob)) return dob; // already MM/DD/YYYY
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(dob)) {
|
||||
const datePart = dob?.split("T")[0]; // safe optional chaining
|
||||
if (!datePart) return "";
|
||||
const [year, month, day] = datePart.split("-");
|
||||
|
||||
const normalized = formatLocalDate(parseLocalDate(dob));
|
||||
|
||||
// If it's already MM/DD/YYYY, leave it alone
|
||||
if (/^\d{2}\/\d{2}\/\d{4}$/.test(normalized)) return normalized;
|
||||
|
||||
// If it's yyyy-MM-dd, swap order to MM/DD/YYYY
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
|
||||
const [year, month, day] = normalized.split("-");
|
||||
return `${month}/${day}/${year}`;
|
||||
}
|
||||
return dob;
|
||||
|
||||
return normalized;
|
||||
};
|
||||
|
||||
// MAIN FORM INITIAL STATE
|
||||
@@ -226,7 +179,7 @@ export function ClaimForm({
|
||||
userId: Number(user?.id),
|
||||
staffId: Number(staff?.id),
|
||||
patientName: `${patient?.firstName} ${patient?.lastName}`.trim(),
|
||||
memberId: patient?.insuranceId,
|
||||
memberId: patient?.insuranceId ?? "",
|
||||
dateOfBirth: formatDOB(patient?.dateOfBirth),
|
||||
remarks: "",
|
||||
serviceDate: serviceDate,
|
||||
@@ -239,7 +192,9 @@ export function ClaimForm({
|
||||
oralCavityArea: "",
|
||||
toothNumber: "",
|
||||
toothSurface: "",
|
||||
billedAmount: 0,
|
||||
totalBilled: new Decimal(0),
|
||||
totalAdjusted: new Decimal(0),
|
||||
totalPaid: new Decimal(0),
|
||||
})),
|
||||
uploadedFiles: [],
|
||||
});
|
||||
@@ -251,10 +206,10 @@ export function ClaimForm({
|
||||
`${patient.firstName || ""} ${patient.lastName || ""}`.trim();
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
patientId: Number(patient.id),
|
||||
patientName: fullName,
|
||||
dateOfBirth: formatDOB(patient.dateOfBirth),
|
||||
memberId: patient.insuranceId || "",
|
||||
patientId: patient.id,
|
||||
}));
|
||||
}
|
||||
}, [patient]);
|
||||
@@ -266,16 +221,16 @@ export function ClaimForm({
|
||||
|
||||
const updateServiceLine = (
|
||||
index: number,
|
||||
field: keyof ServiceLine,
|
||||
field: keyof InputServiceLine,
|
||||
value: any
|
||||
) => {
|
||||
const updatedLines = [...form.serviceLines];
|
||||
|
||||
if (updatedLines[index]) {
|
||||
if (field === "billedAmount") {
|
||||
if (field === "totalBilled") {
|
||||
const num = typeof value === "string" ? parseFloat(value) : value;
|
||||
const rounded = Math.round((isNaN(num) ? 0 : num) * 100) / 100;
|
||||
updatedLines[index][field] = rounded;
|
||||
updatedLines[index][field] = new Decimal(rounded);
|
||||
} else {
|
||||
updatedLines[index][field] = value;
|
||||
}
|
||||
@@ -672,16 +627,20 @@ export function ClaimForm({
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="$0.00"
|
||||
value={line.billedAmount === 0 ? "" : line.billedAmount}
|
||||
value={
|
||||
line.totalBilled?.toNumber() === 0
|
||||
? ""
|
||||
: line.totalBilled?.toNumber()
|
||||
}
|
||||
onChange={(e) => {
|
||||
updateServiceLine(i, "billedAmount", e.target.value);
|
||||
updateServiceLine(i, "totalBilled", e.target.value);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
const val = parseFloat(e.target.value);
|
||||
const rounded = Math.round(val * 100) / 100;
|
||||
updateServiceLine(
|
||||
i,
|
||||
"billedAmount",
|
||||
"totalBilled",
|
||||
isNaN(rounded) ? 0 : rounded
|
||||
);
|
||||
}}
|
||||
@@ -702,7 +661,9 @@ export function ClaimForm({
|
||||
oralCavityArea: "",
|
||||
toothNumber: "",
|
||||
toothSurface: "",
|
||||
billedAmount: 0,
|
||||
totalBilled: new Decimal(0),
|
||||
totalAdjusted: new Decimal(0),
|
||||
totalPaid: new Decimal(0),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
@@ -6,31 +6,9 @@ import {
|
||||
DialogDescription,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
type ClaimWithServiceLines = Claim & {
|
||||
serviceLines: {
|
||||
id: number;
|
||||
claimId: number;
|
||||
procedureCode: string;
|
||||
procedureDate: Date;
|
||||
oralCavityArea: string | null;
|
||||
toothNumber: string | null;
|
||||
toothSurface: string | null;
|
||||
billedAmount: number;
|
||||
}[];
|
||||
staff: Staff | null;
|
||||
};
|
||||
import { ClaimWithServiceLines } from "@repo/db/types";
|
||||
|
||||
type ClaimViewModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -205,14 +183,17 @@ export default function ClaimViewModal({
|
||||
)}
|
||||
<p>
|
||||
<span className="text-gray-500">Billed Amount:</span>{" "}
|
||||
${line.billedAmount.toFixed(2)}
|
||||
${line.totalBilled.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
<div className="text-right font-semibold text-gray-900 pt-2 border-t mt-4">
|
||||
Total Billed Amount: $
|
||||
{claim.serviceLines
|
||||
.reduce((total, line) => total + line.billedAmount, 0)
|
||||
.reduce(
|
||||
(total, line) => total + line.totalBilled?.toNumber(),
|
||||
0
|
||||
)
|
||||
.toFixed(2)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import ClaimsRecentTable from "./claims-recent-table";
|
||||
import { PatientTable } from "../patients/patient-table";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -10,19 +8,15 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../ui/card";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
import { Patient } from "@repo/db/types";
|
||||
|
||||
interface ClaimsOfPatientModalProps {
|
||||
onNewClaim?: (patientId: number) => void;
|
||||
}
|
||||
|
||||
export default function ClaimsOfPatientModal({ onNewClaim }: ClaimsOfPatientModalProps) {
|
||||
export default function ClaimsOfPatientModal({
|
||||
onNewClaim,
|
||||
}: ClaimsOfPatientModalProps) {
|
||||
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [claimsPage, setClaimsPage] = useState(1);
|
||||
|
||||
@@ -28,45 +28,13 @@ import {
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination";
|
||||
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
||||
import {
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
ClaimStatusSchema,
|
||||
StaffUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import LoadingScreen from "@/components/ui/LoadingScreen";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
import ClaimViewModal from "./claim-view-modal";
|
||||
import ClaimEditModal from "./claim-edit-modal";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
export type ClaimStatus = z.infer<typeof ClaimStatusSchema>;
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
type ClaimWithServiceLines = Claim & {
|
||||
serviceLines: {
|
||||
id: number;
|
||||
claimId: number;
|
||||
procedureCode: string;
|
||||
procedureDate: Date;
|
||||
oralCavityArea: string | null;
|
||||
toothNumber: string | null;
|
||||
toothSurface: string | null;
|
||||
billedAmount: number;
|
||||
}[];
|
||||
staff: Staff | null;
|
||||
};
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
import { Claim, ClaimStatus, ClaimWithServiceLines } from "@repo/db/types";
|
||||
|
||||
interface ClaimApiResponse {
|
||||
claims: ClaimWithServiceLines[];
|
||||
@@ -314,7 +282,7 @@ export default function ClaimsRecentTable({
|
||||
|
||||
const getTotalBilled = (claim: ClaimWithServiceLines) => {
|
||||
return claim.serviceLines.reduce(
|
||||
(sum, line) => sum + (line.billedAmount || 0),
|
||||
(sum, line) => sum + (line.totalBilled?.toNumber() || 0),
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { Upload, File, X, FilePlus } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { cn } from '@/lib/utils';
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { Upload, File, X, FilePlus } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FileUploadZoneProps {
|
||||
onFileUpload: (file: File) => void;
|
||||
@@ -10,10 +10,10 @@ interface FileUploadZoneProps {
|
||||
acceptedFileTypes?: string;
|
||||
}
|
||||
|
||||
export function FileUploadZone({
|
||||
onFileUpload,
|
||||
isUploading,
|
||||
acceptedFileTypes = "application/pdf"
|
||||
export function FileUploadZone({
|
||||
onFileUpload,
|
||||
isUploading,
|
||||
acceptedFileTypes = "application/pdf",
|
||||
}: FileUploadZoneProps) {
|
||||
const { toast } = useToast();
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
@@ -32,13 +32,16 @@ export function FileUploadZone({
|
||||
setIsDragging(false);
|
||||
}, []);
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!isDragging) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
}, [isDragging]);
|
||||
const handleDragOver = useCallback(
|
||||
(e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!isDragging) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
},
|
||||
[isDragging]
|
||||
);
|
||||
|
||||
const validateFile = (file: File) => {
|
||||
// Check file type
|
||||
@@ -46,49 +49,55 @@ export function FileUploadZone({
|
||||
toast({
|
||||
title: "Invalid file type",
|
||||
description: "Please upload a PDF file.",
|
||||
variant: "destructive"
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check file size (limit to 5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
toast({
|
||||
title: "File too large",
|
||||
description: "File size should be less than 5MB.",
|
||||
variant: "destructive"
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
||||
const file = e.dataTransfer.files[0];
|
||||
|
||||
if (validateFile(file)) {
|
||||
setUploadedFile(file);
|
||||
onFileUpload(file);
|
||||
}
|
||||
}
|
||||
}, [onFileUpload, acceptedFileTypes, toast]);
|
||||
const handleDrop = useCallback(
|
||||
(e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
|
||||
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (validateFile(file)) {
|
||||
setUploadedFile(file);
|
||||
onFileUpload(file);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
|
||||
const file = e.dataTransfer.files[0];
|
||||
|
||||
if (validateFile(file)) {
|
||||
setUploadedFile(file);
|
||||
onFileUpload(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [onFileUpload, acceptedFileTypes, toast]);
|
||||
},
|
||||
[onFileUpload, acceptedFileTypes, toast]
|
||||
);
|
||||
|
||||
const handleFileSelect = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (validateFile(file)) {
|
||||
setUploadedFile(file);
|
||||
onFileUpload(file);
|
||||
}
|
||||
}
|
||||
},
|
||||
[onFileUpload, acceptedFileTypes, toast]
|
||||
);
|
||||
|
||||
const handleBrowseClick = () => {
|
||||
if (fileInputRef.current) {
|
||||
@@ -109,11 +118,13 @@ export function FileUploadZone({
|
||||
onChange={handleFileSelect}
|
||||
accept={acceptedFileTypes}
|
||||
/>
|
||||
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"border-2 border-dashed rounded-lg p-8 flex flex-col items-center justify-center text-center transition-colors",
|
||||
isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25",
|
||||
isDragging
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-muted-foreground/25",
|
||||
uploadedFile ? "bg-success/5" : "hover:bg-muted/40",
|
||||
isUploading && "opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
@@ -135,7 +146,7 @@ export function FileUploadZone({
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="relative">
|
||||
<File className="h-12 w-12 text-primary" />
|
||||
<button
|
||||
<button
|
||||
className="absolute -top-2 -right-2 bg-background rounded-full p-1 shadow-sm border"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -166,9 +177,9 @@ export function FileUploadZone({
|
||||
Or click to browse files
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleBrowseClick();
|
||||
@@ -184,4 +195,4 @@ export function FileUploadZone({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ interface CredentialsModalProps {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function CredentialsModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubmit,
|
||||
export function CredentialsModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubmit,
|
||||
providerName,
|
||||
isLoading = false
|
||||
isLoading = false,
|
||||
}: CredentialsModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
@@ -51,10 +51,11 @@ export function CredentialsModal({
|
||||
<DialogHeader>
|
||||
<DialogTitle>Insurance Portal Login</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter your credentials for {providerName} insurance portal to check patient eligibility automatically.
|
||||
Enter your credentials for {providerName} insurance portal to check
|
||||
patient eligibility automatically.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
@@ -68,7 +69,7 @@ export function CredentialsModal({
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<div className="relative">
|
||||
@@ -97,7 +98,7 @@ export function CredentialsModal({
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -118,4 +119,4 @@ export function CredentialsModal({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { LayoutDashboard, Users, Calendar, Settings, FileCheck, Shield, CreditCard, FolderOpen } from "lucide-react";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Users,
|
||||
Calendar,
|
||||
Settings,
|
||||
FileCheck,
|
||||
Shield,
|
||||
CreditCard,
|
||||
FolderOpen,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -62,29 +71,36 @@ export function Sidebar({ isMobileOpen, setIsMobileOpen }: SidebarProps) {
|
||||
)}
|
||||
>
|
||||
<div className="p-4 border-b border-gray-200 flex items-center space-x-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-5 w-5 text-primary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-5 w-5 text-primary"
|
||||
>
|
||||
<path d="M12 14c-1.65 0-3-1.35-3-3V5c0-1.65 1.35-3 3-3s3 1.35 3 3v6c0 1.65-1.35 3-3 3Z" />
|
||||
<path d="M19 14v-4a7 7 0 0 0-14 0v4" />
|
||||
<path d="M12 19c-5 0-8-2-9-5.5m18 0c-1 3.5-4 5.5-9 5.5Z" />
|
||||
</svg>
|
||||
<h1 className="text-lg font-medium text-primary">DentalConnect</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-2">
|
||||
|
||||
<nav>
|
||||
{navItems.map((item) => (
|
||||
<div key={item.path}>
|
||||
<Link
|
||||
to={item.path}
|
||||
onClick={() => setIsMobileOpen(false)}
|
||||
>
|
||||
<div className={cn(
|
||||
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
|
||||
location === item.path
|
||||
? "text-primary font-medium border-l-2 border-primary"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
)}>
|
||||
<Link to={item.path} onClick={() => setIsMobileOpen(false)}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-3 p-2 rounded-md pl-3 mb-1 transition-colors cursor-pointer",
|
||||
location === item.path
|
||||
? "text-primary font-medium border-l-2 border-primary"
|
||||
: "text-gray-600 hover:bg-gray-100"
|
||||
)}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.name}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bell, Menu} from "lucide-react";
|
||||
import { Bell, Menu } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
@@ -11,8 +11,6 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
|
||||
|
||||
interface TopAppBarProps {
|
||||
toggleMobileMenu: () => void;
|
||||
}
|
||||
@@ -33,34 +31,39 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
||||
<header className="bg-white shadow-sm z-10">
|
||||
<div className="flex items-center justify-between h-16 px-4">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden mr-2"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden mr-2"
|
||||
onClick={toggleMobileMenu}
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="md:hidden text-lg font-medium text-primary">DentalConnect</h1>
|
||||
<h1 className="md:hidden text-lg font-medium text-primary">
|
||||
DentalConnect
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="hidden md:flex md:flex-1 items-center justify-center">
|
||||
{/* Search bar removed */}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="relative p-0 h-9 w-9 rounded-full"
|
||||
>
|
||||
<Bell className="h-5 w-5" />
|
||||
<span className="absolute top-0 right-0 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></span>
|
||||
</Button>
|
||||
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative p-0 h-8 w-8 rounded-full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="relative p-0 h-8 w-8 rounded-full"
|
||||
>
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src="" alt={user?.username} />
|
||||
<AvatarFallback className="bg-primary text-white">
|
||||
@@ -72,8 +75,9 @@ export function TopAppBar({ toggleMobileMenu }: TopAppBarProps) {
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem>{user?.username}</DropdownMenuItem>
|
||||
<DropdownMenuItem>My Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
||||
Account Settings</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setLocation("/settings")}>
|
||||
Account Settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
Log out
|
||||
|
||||
@@ -14,39 +14,10 @@ import {
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { PatientForm, PatientFormRef } from "./patient-form";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { PatientForm, PatientFormRef } from "./patient-form";
|
||||
import { X, Calendar } from "lucide-react";
|
||||
import { useLocation } from "wouter";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
import { InsertPatient, Patient, UpdatePatient } from "@repo/db/types";
|
||||
|
||||
interface AddPatientModalProps {
|
||||
open: boolean;
|
||||
@@ -108,12 +79,11 @@ export const AddPatientModal = forwardRef<
|
||||
};
|
||||
|
||||
const handleSaveAndSchedule = () => {
|
||||
setSaveAndSchedule(true);
|
||||
if (patientFormRef.current) {
|
||||
patientFormRef.current.submit();
|
||||
}
|
||||
};
|
||||
|
||||
setSaveAndSchedule(true);
|
||||
if (patientFormRef.current) {
|
||||
patientFormRef.current.submit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
Form,
|
||||
@@ -32,34 +30,14 @@ import { format } from "date-fns";
|
||||
import { Button } from "../ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
import {
|
||||
InsertPatient,
|
||||
insertPatientSchema,
|
||||
Patient,
|
||||
UpdatePatient,
|
||||
updatePatientSchema,
|
||||
} from "@repo/db/types";
|
||||
import { z } from "zod";
|
||||
|
||||
interface PatientFormProps {
|
||||
patient?: Patient;
|
||||
|
||||
@@ -85,25 +85,25 @@ export function PatientSearch({
|
||||
<div className="relative flex-1">
|
||||
{searchBy === "dob" ? (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleSearch();
|
||||
}}
|
||||
className={cn(
|
||||
"w-full pl-3 pr-20 text-left font-normal",
|
||||
!searchTerm && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{searchTerm ? (
|
||||
format(new Date(searchTerm), "PPP")
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") handleSearch();
|
||||
}}
|
||||
className={cn(
|
||||
"w-full pl-3 pr-20 text-left font-normal",
|
||||
!searchTerm && "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{searchTerm ? (
|
||||
format(new Date(searchTerm), "PPP")
|
||||
) : (
|
||||
<span>Pick a date</span>
|
||||
)}
|
||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-4">
|
||||
<Calendar
|
||||
mode="single"
|
||||
|
||||
@@ -18,8 +18,6 @@ import {
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import LoadingScreen from "../ui/LoadingScreen";
|
||||
@@ -39,25 +37,7 @@ import { useDebounce } from "use-debounce";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Checkbox } from "../ui/checkbox";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
import { Patient, UpdatePatient } from "@repo/db/types";
|
||||
|
||||
interface PatientApiResponse {
|
||||
patients: Patient[];
|
||||
@@ -114,7 +94,7 @@ export function PatientTable({
|
||||
const handleSelectPatient = (patient: Patient) => {
|
||||
const isSelected = selectedPatientId === patient.id;
|
||||
const newSelectedId = isSelected ? null : patient.id;
|
||||
setSelectedPatientId(newSelectedId);
|
||||
setSelectedPatientId(Number(newSelectedId));
|
||||
|
||||
if (onSelectPatient) {
|
||||
onSelectPatient(isSelected ? null : patient);
|
||||
@@ -258,7 +238,7 @@ export function PatientTable({
|
||||
if (currentPatient && user) {
|
||||
const { id, ...sanitizedPatient } = patient;
|
||||
updatePatientMutation.mutate({
|
||||
id: currentPatient.id,
|
||||
id: Number(currentPatient.id),
|
||||
patient: sanitizedPatient,
|
||||
});
|
||||
} else {
|
||||
@@ -288,7 +268,7 @@ export function PatientTable({
|
||||
|
||||
const handleConfirmDeletePatient = async () => {
|
||||
if (currentPatient) {
|
||||
deletePatientMutation.mutate(currentPatient.id);
|
||||
deletePatientMutation.mutate(Number(currentPatient.id));
|
||||
} else {
|
||||
toast({
|
||||
title: "Error",
|
||||
@@ -424,7 +404,7 @@ export function PatientTable({
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<Avatar
|
||||
className={`h-10 w-10 ${getAvatarColor(patient.id)}`}
|
||||
className={`h-10 w-10 ${getAvatarColor(Number(patient.id))}`}
|
||||
>
|
||||
<AvatarFallback className="text-white">
|
||||
{getInitials(patient.firstName, patient.lastName)}
|
||||
@@ -436,7 +416,7 @@ export function PatientTable({
|
||||
{patient.firstName} {patient.lastName}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
PID-{patient.id.toString().padStart(4, "0")}
|
||||
PID-{patient.id?.toString().padStart(4, "0")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -505,16 +485,16 @@ export function PatientTable({
|
||||
</Button>
|
||||
)}
|
||||
{allowNewClaim && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNewClaim?.(patient.id)}
|
||||
className="text-green-600 hover:text-green-800 hover:bg-green-50"
|
||||
aria-label="New Claim"
|
||||
>
|
||||
<FileCheck className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => onNewClaim?.(Number(patient.id))}
|
||||
className="text-green-600 hover:text-green-800 hover:bg-green-50"
|
||||
aria-label="New Claim"
|
||||
>
|
||||
<FileCheck className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
{allowView && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -558,7 +538,7 @@ export function PatientTable({
|
||||
{currentPatient.firstName} {currentPatient.lastName}
|
||||
</h3>
|
||||
<p className="text-gray-500">
|
||||
Patient ID: {currentPatient.id.toString().padStart(4, "0")}
|
||||
Patient ID: {currentPatient.id?.toString().padStart(4, "0")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -587,8 +567,10 @@ export function PatientTable({
|
||||
: "text-red-600"
|
||||
} font-medium`}
|
||||
>
|
||||
{currentPatient.status.charAt(0).toUpperCase() +
|
||||
currentPatient.status.slice(1)}
|
||||
{currentPatient.status
|
||||
? currentPatient.status.charAt(0).toUpperCase() +
|
||||
currentPatient.status.slice(1)
|
||||
: "Unknown"}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -706,7 +688,7 @@ export function PatientTable({
|
||||
isOpen={isDeletePatientOpen}
|
||||
onConfirm={handleConfirmDeletePatient}
|
||||
onCancel={() => setIsDeletePatientOpen(false)}
|
||||
entityName={currentPatient?.name}
|
||||
entityName={currentPatient?.firstName}
|
||||
/>
|
||||
|
||||
{/* Pagination */}
|
||||
@@ -731,25 +713,25 @@ export function PatientTable({
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
|
||||
{getPageNumbers(currentPage, totalPages).map((page, idx) => (
|
||||
<PaginationItem key={idx}>
|
||||
{page === "..." ? (
|
||||
<span className="px-2 text-gray-500">...</span>
|
||||
) : (
|
||||
<PaginationLink
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setCurrentPage(page as number);
|
||||
}}
|
||||
isActive={currentPage === page}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
)}
|
||||
</PaginationItem>
|
||||
))}
|
||||
<PaginationItem key={idx}>
|
||||
{page === "..." ? (
|
||||
<span className="px-2 text-gray-500">...</span>
|
||||
) : (
|
||||
<PaginationLink
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setCurrentPage(page as number);
|
||||
}}
|
||||
isActive={currentPage === page}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
)}
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
|
||||
@@ -6,11 +6,20 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { formatDateToHumanReadable } from "@/utils/dateUtils";
|
||||
import {
|
||||
formatDateToHumanReadable,
|
||||
formatLocalDate,
|
||||
parseLocalDate,
|
||||
} from "@/utils/dateUtils";
|
||||
import React, { useState } from "react";
|
||||
import { paymentStatusOptions, PaymentWithExtras } from "@repo/db/types";
|
||||
import { PaymentStatus, PaymentMethod } from "@repo/db/types";
|
||||
|
||||
import {
|
||||
PaymentStatus,
|
||||
paymentStatusOptions,
|
||||
PaymentMethod,
|
||||
paymentMethodOptions,
|
||||
PaymentWithExtras,
|
||||
NewTransactionPayload,
|
||||
} from "@repo/db/types";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -26,7 +35,7 @@ type PaymentEditModalProps = {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onClose: () => void;
|
||||
onEditServiceLine: (updatedPayment: PaymentWithExtras) => void;
|
||||
onEditServiceLine: (payload: NewTransactionPayload) => void;
|
||||
payment: PaymentWithExtras | null;
|
||||
};
|
||||
|
||||
@@ -40,143 +49,88 @@ export default function PaymentEditModal({
|
||||
if (!payment) return null;
|
||||
|
||||
const [expandedLineId, setExpandedLineId] = useState<number | null>(null);
|
||||
const [updatedPaidAmounts, setUpdatedPaidAmounts] = useState<
|
||||
Record<number, number>
|
||||
>({});
|
||||
const [updatedAdjustedAmounts, setUpdatedAdjustedAmounts] = useState<
|
||||
Record<number, number>
|
||||
>({});
|
||||
const [updatedNotes, setUpdatedNotes] = useState<Record<number, string>>({});
|
||||
const [updatedPaymentStatus, setUpdatedPaymentStatus] =
|
||||
useState<PaymentStatus>(payment?.status ?? "PENDING");
|
||||
const [updatedTransactions, setUpdatedTransactions] = useState(
|
||||
() => payment?.transactions ?? []
|
||||
const [paymentStatus, setPaymentStatus] = React.useState<PaymentStatus>(
|
||||
payment.status
|
||||
);
|
||||
|
||||
type DraftPaymentData = {
|
||||
paidAmount: number;
|
||||
adjustedAmount?: number;
|
||||
notes?: string;
|
||||
paymentStatus?: PaymentStatus;
|
||||
payerName?: string;
|
||||
method: PaymentMethod;
|
||||
receivedDate: string;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const [serviceLineDrafts, setServiceLineDrafts] = useState<Record<number, any>>({});
|
||||
|
||||
|
||||
const totalPaid = payment.transactions.reduce(
|
||||
(sum, tx) =>
|
||||
sum +
|
||||
tx.serviceLinePayments.reduce((s, sp) => s + Number(sp.paidAmount), 0),
|
||||
0
|
||||
);
|
||||
|
||||
const totalBilled = payment.claim.serviceLines.reduce(
|
||||
(sum, line) => sum + line.billedAmount,
|
||||
0
|
||||
);
|
||||
|
||||
const totalDue = totalBilled - totalPaid;
|
||||
const [formState, setFormState] = useState(() => {
|
||||
return {
|
||||
serviceLineId: 0,
|
||||
transactionId: "",
|
||||
paidAmount: 0,
|
||||
adjustedAmount: 0,
|
||||
method: paymentMethodOptions[1] as PaymentMethod,
|
||||
receivedDate: formatLocalDate(new Date()),
|
||||
payerName: "",
|
||||
notes: "",
|
||||
};
|
||||
});
|
||||
|
||||
const handleEditServiceLine = (lineId: number) => {
|
||||
setExpandedLineId(lineId === expandedLineId ? null : lineId);
|
||||
};
|
||||
|
||||
const handleFieldChange = (lineId: number, field: string, value: any) => {
|
||||
setServiceLineDrafts((prev) => ({
|
||||
...prev,
|
||||
[lineId]: {
|
||||
...prev[lineId],
|
||||
[field]: value,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
// const handleSavePayment = (lineId: number) => {
|
||||
// const newPaidAmount = updatedPaidAmounts[lineId];
|
||||
// const newAdjustedAmount = updatedAdjustedAmounts[lineId] ?? 0;
|
||||
// const newNotes = updatedNotes[lineId] ?? "";
|
||||
|
||||
// if (newPaidAmount == null || isNaN(newPaidAmount)) return;
|
||||
|
||||
// const updatedTxs = updatedTransactions.map((tx) => ({
|
||||
// ...tx,
|
||||
// serviceLinePayments: tx.serviceLinePayments.map((sp) =>
|
||||
// sp.serviceLineId === lineId
|
||||
// ? {
|
||||
// ...sp,
|
||||
// paidAmount: new Decimal(newPaidAmount),
|
||||
// adjustedAmount: new Decimal(newAdjustedAmount),
|
||||
// notes: newNotes,
|
||||
// }
|
||||
// : sp
|
||||
// ),
|
||||
// }));
|
||||
|
||||
// const updatedPayment: PaymentWithExtras = {
|
||||
// ...payment,
|
||||
// transactions: updatedTxs,
|
||||
// status: updatedPaymentStatus,
|
||||
// };
|
||||
|
||||
// setUpdatedTransactions(updatedTxs);
|
||||
// onEditServiceLine(updatedPayment);
|
||||
// setExpandedLineId(null);
|
||||
// };
|
||||
|
||||
const handleSavePayment = async (lineId: number) => {
|
||||
const data = serviceLineDrafts[lineId];
|
||||
if (!data || !data.paidAmount || !data.method || !data.receivedDate) {
|
||||
console.log("please fill al")
|
||||
if (expandedLineId === lineId) {
|
||||
// Closing current line
|
||||
setExpandedLineId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionPayload = {
|
||||
// Find line data
|
||||
const line = payment.claim.serviceLines.find((sl) => sl.id === lineId);
|
||||
if (!line) return;
|
||||
|
||||
// updating form to show its data, while expanding.
|
||||
setFormState({
|
||||
serviceLineId: line.id,
|
||||
transactionId: "",
|
||||
paidAmount: Number(line.totalDue) > 0 ? Number(line.totalDue) : 0,
|
||||
adjustedAmount: 0,
|
||||
method: paymentMethodOptions[1] as PaymentMethod,
|
||||
receivedDate: formatLocalDate(new Date()),
|
||||
payerName: "",
|
||||
notes: "",
|
||||
});
|
||||
|
||||
setExpandedLineId(lineId);
|
||||
};
|
||||
|
||||
const updateField = (field: string, value: any) => {
|
||||
setFormState((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSavePayment = async () => {
|
||||
if (!formState.serviceLineId) {
|
||||
toast({ title: "Error", description: "No service line selected." });
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: NewTransactionPayload = {
|
||||
paymentId: payment.id,
|
||||
amount: data.paidAmount + (data.adjustedAmount ?? 0),
|
||||
method: data.method,
|
||||
payerName: data.payerName,
|
||||
notes: data.notes,
|
||||
receivedDate: new Date(data.receivedDate),
|
||||
serviceLinePayments: [
|
||||
status: paymentStatus,
|
||||
serviceLineTransactions: [
|
||||
{
|
||||
serviceLineId: lineId,
|
||||
paidAmount: data.paidAmount,
|
||||
adjustedAmount: data.adjustedAmount ?? 0,
|
||||
notes: data.notes,
|
||||
serviceLineId: formState.serviceLineId,
|
||||
transactionId: formState.transactionId || undefined,
|
||||
paidAmount: Number(formState.paidAmount),
|
||||
adjustedAmount: Number(formState.adjustedAmount) || 0,
|
||||
method: formState.method,
|
||||
receivedDate: parseLocalDate(formState.receivedDate),
|
||||
payerName: formState.payerName || undefined,
|
||||
notes: formState.notes || undefined,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
await onEditServiceLine(transactionPayload);
|
||||
await onEditServiceLine(payload);
|
||||
setExpandedLineId(null);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
console.error(err);
|
||||
toast({ title: "Error", description: "Failed to save payment." });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const renderInput = (label: string, type: string, lineId: number, field: string, step?: string) => (
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">{label}</label>
|
||||
<Input
|
||||
type={type}
|
||||
step={step}
|
||||
value={serviceLineDrafts[lineId]?.[field] ?? ""}
|
||||
onChange={(e) =>
|
||||
handleFieldChange(lineId, field, type === "number" ? parseFloat(e.target.value) : e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
|
||||
@@ -200,6 +154,10 @@ export default function PaymentEditModal({
|
||||
Service Date:{" "}
|
||||
{formatDateToHumanReadable(payment.claim.serviceDate)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Notes:</span>{" "}
|
||||
{payment.notes || "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Summary */}
|
||||
@@ -209,22 +167,22 @@ export default function PaymentEditModal({
|
||||
<div className="mt-2 space-y-1">
|
||||
<p>
|
||||
<span className="text-gray-500">Total Billed:</span> $
|
||||
{totalBilled.toFixed(2)}
|
||||
{payment.totalBilled.toNumber().toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Total Paid:</span> $
|
||||
{totalPaid.toFixed(2)}
|
||||
{payment.totalPaid.toNumber().toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Total Due:</span> $
|
||||
{totalDue.toFixed(2)}
|
||||
{payment.totalDue.toNumber().toFixed(2)}
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<label className="text-sm text-gray-600">Status</label>
|
||||
<Select
|
||||
value={updatedPaymentStatus}
|
||||
value={paymentStatus}
|
||||
onValueChange={(value: PaymentStatus) =>
|
||||
setUpdatedPaymentStatus(value)
|
||||
setPaymentStatus(value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
@@ -246,18 +204,16 @@ export default function PaymentEditModal({
|
||||
<h4 className="font-medium text-gray-900">Metadata</h4>
|
||||
<div className="mt-2 space-y-1">
|
||||
<p>
|
||||
<span className="text-gray-500">Received Date:</span>{" "}
|
||||
{payment.receivedDate
|
||||
? formatDateToHumanReadable(payment.receivedDate)
|
||||
<span className="text-gray-500">Created At:</span>{" "}
|
||||
{payment.createdAt
|
||||
? formatDateToHumanReadable(payment.createdAt)
|
||||
: "N/A"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Method:</span>{" "}
|
||||
{payment.paymentMethod ?? "N/A"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Notes:</span>{" "}
|
||||
{payment.notes || "N/A"}
|
||||
<span className="text-gray-500">Last Upadated At:</span>{" "}
|
||||
{payment.updatedAt
|
||||
? formatDateToHumanReadable(payment.updatedAt)
|
||||
: "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,22 +226,6 @@ export default function PaymentEditModal({
|
||||
{payment.claim.serviceLines.length > 0 ? (
|
||||
<>
|
||||
{payment.claim.serviceLines.map((line) => {
|
||||
const linePayments = payment.transactions.flatMap((tx) =>
|
||||
tx.serviceLinePayments.filter(
|
||||
(sp) => sp.serviceLineId === line.id
|
||||
)
|
||||
);
|
||||
|
||||
const paidAmount = linePayments.reduce(
|
||||
(sum, sp) => sum + Number(sp.paidAmount),
|
||||
0
|
||||
);
|
||||
const adjusted = linePayments.reduce(
|
||||
(sum, sp) => sum + Number(sp.adjustedAmount),
|
||||
0
|
||||
);
|
||||
const due = line.billedAmount - paidAmount;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={line.id}
|
||||
@@ -297,19 +237,19 @@ export default function PaymentEditModal({
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Billed:</span> $
|
||||
{line.billedAmount.toFixed(2)}
|
||||
{line.totalBilled.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Paid:</span> $
|
||||
{paidAmount.toFixed(2)}
|
||||
{line.totalPaid.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Adjusted:</span> $
|
||||
{adjusted.toFixed(2)}
|
||||
{line.totalAdjusted.toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Due:</span> $
|
||||
{due.toFixed(2)}
|
||||
{line.totalDue.toFixed(2)}
|
||||
</p>
|
||||
|
||||
<div className="pt-2">
|
||||
@@ -337,12 +277,12 @@ export default function PaymentEditModal({
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="Paid Amount"
|
||||
defaultValue={paidAmount}
|
||||
defaultValue={formState.paidAmount}
|
||||
onChange={(e) =>
|
||||
setUpdatedPaidAmounts({
|
||||
...updatedPaidAmounts,
|
||||
[line.id]: parseFloat(e.target.value),
|
||||
})
|
||||
updateField(
|
||||
"paidAmount",
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -358,12 +298,65 @@ export default function PaymentEditModal({
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="Adjusted Amount"
|
||||
defaultValue={adjusted}
|
||||
defaultValue={formState.adjustedAmount}
|
||||
onChange={(e) =>
|
||||
setUpdatedAdjustedAmounts({
|
||||
...updatedAdjustedAmounts,
|
||||
[line.id]: parseFloat(e.target.value),
|
||||
})
|
||||
updateField(
|
||||
"adjustedAmount",
|
||||
parseFloat(e.target.value)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">
|
||||
Payment Method
|
||||
</label>
|
||||
<Select
|
||||
value={formState.method}
|
||||
onValueChange={(value: PaymentMethod) =>
|
||||
updateField("method", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{paymentMethodOptions.map((methodOption) => (
|
||||
<SelectItem
|
||||
key={methodOption}
|
||||
value={methodOption}
|
||||
>
|
||||
{methodOption}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">
|
||||
Received Date
|
||||
</label>
|
||||
<Input
|
||||
type="date"
|
||||
value={formState.receivedDate}
|
||||
onChange={(e) =>
|
||||
updateField("receivedDate", e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-medium">
|
||||
Payer Name
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Payer Name"
|
||||
value={formState.payerName}
|
||||
onChange={(e) =>
|
||||
updateField("payerName", e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -380,16 +373,13 @@ export default function PaymentEditModal({
|
||||
type="text"
|
||||
placeholder="Notes"
|
||||
onChange={(e) =>
|
||||
setUpdatedNotes({
|
||||
...updatedNotes,
|
||||
[line.id]: e.target.value,
|
||||
})
|
||||
updateField("notes", e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => handleSavePayment(line.id)}
|
||||
onClick={() => handleSavePayment()}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
@@ -409,8 +399,8 @@ export default function PaymentEditModal({
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 pt-6">All Transactions</h4>
|
||||
<div className="mt-2 space-y-2">
|
||||
{payment.transactions.length > 0 ? (
|
||||
payment.transactions.map((tx) => (
|
||||
{payment.serviceLineTransactions.length > 0 ? (
|
||||
payment.serviceLineTransactions.map((tx) => (
|
||||
<div
|
||||
key={tx.id}
|
||||
className="border p-3 rounded-md bg-white shadow-sm"
|
||||
@@ -420,18 +410,27 @@ export default function PaymentEditModal({
|
||||
{formatDateToHumanReadable(tx.receivedDate)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Amount:</span> $
|
||||
{Number(tx.amount).toFixed(2)}
|
||||
<span className="text-gray-500">Paid Amount:</span> $
|
||||
{Number(tx.paidAmount).toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Adjusted Amount:</span> $
|
||||
{Number(tx.adjustedAmount).toFixed(2)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="text-gray-500">Method:</span> {tx.method}
|
||||
</p>
|
||||
{tx.serviceLinePayments.map((sp) => (
|
||||
<p key={sp.id} className="text-sm text-gray-600 ml-2">
|
||||
• Applied ${Number(sp.paidAmount).toFixed(2)} to service
|
||||
line ID {sp.serviceLineId}
|
||||
{tx.payerName && (
|
||||
<p>
|
||||
<span className="text-gray-500">Payer Name:</span>{" "}
|
||||
{tx.payerName}
|
||||
</p>
|
||||
))}
|
||||
)}
|
||||
{tx.notes && (
|
||||
<p>
|
||||
<span className="text-gray-500">Notes:</span> {tx.notes}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -31,12 +31,11 @@ import {
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { DeleteConfirmationDialog } from "../ui/deleteDialog";
|
||||
import PaymentViewModal from "./payment-view-modal";
|
||||
import PaymentEditModal from "./payment-edit-modal";
|
||||
import LoadingScreen from "../ui/LoadingScreen";
|
||||
import {
|
||||
ClaimStatus,
|
||||
ClaimWithServiceLines,
|
||||
Payment,
|
||||
NewTransactionPayload,
|
||||
PaymentWithExtras,
|
||||
} from "@repo/db/types";
|
||||
import EditPaymentModal from "./payment-edit-modal";
|
||||
@@ -118,10 +117,14 @@ export default function PaymentsRecentTable({
|
||||
});
|
||||
|
||||
const updatePaymentMutation = useMutation({
|
||||
mutationFn: async (payment: PaymentWithExtras) => {
|
||||
const response = await apiRequest("PUT", `/api/claims/${payment.id}`, {
|
||||
data: payment,
|
||||
});
|
||||
mutationFn: async (data: NewTransactionPayload) => {
|
||||
const response = await apiRequest(
|
||||
"PUT",
|
||||
`/api/claims/${data.paymentId}`,
|
||||
{
|
||||
data: data,
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.message || "Failed to update Payment");
|
||||
@@ -288,13 +291,6 @@ export default function PaymentsRecentTable({
|
||||
}
|
||||
};
|
||||
|
||||
const getTotalBilled = (claim: ClaimWithServiceLines) => {
|
||||
return claim.serviceLines.reduce(
|
||||
(sum, line) => sum + (line.billedAmount || 0),
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
function getPageNumbers(current: number, total: number): (number | "...")[] {
|
||||
const delta = 2;
|
||||
const range: (number | "...")[] = [];
|
||||
@@ -359,19 +355,9 @@ export default function PaymentsRecentTable({
|
||||
</TableRow>
|
||||
) : (
|
||||
paymentsData?.payments.map((payment) => {
|
||||
const claim = (payment as PaymentWithExtras)
|
||||
.claim as ClaimWithServiceLines;
|
||||
|
||||
const totalBilled = getTotalBilled(claim);
|
||||
|
||||
const totalPaid = (payment as PaymentWithExtras).transactions
|
||||
.flatMap((tx) => tx.serviceLinePayments)
|
||||
.reduce(
|
||||
(sum, sp) => sum + (sp.paidAmount?.toNumber?.() ?? 0),
|
||||
0
|
||||
);
|
||||
|
||||
const outstanding = totalBilled - totalPaid;
|
||||
const totalBilled = payment.totalBilled.toNumber();
|
||||
const totalPaid = payment.totalPaid.toNumber();
|
||||
const totalDue = payment.totalDue.toNumber();
|
||||
|
||||
return (
|
||||
<TableRow key={payment.id}>
|
||||
@@ -401,9 +387,9 @@ export default function PaymentsRecentTable({
|
||||
</span>
|
||||
<span>
|
||||
<strong>Total Due:</strong>{" "}
|
||||
{outstanding > 0 ? (
|
||||
{totalDue > 0 ? (
|
||||
<span className="text-yellow-600">
|
||||
${outstanding.toFixed(2)}
|
||||
${totalDue.toFixed(2)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-green-600">Settled</span>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { Staff } from "@repo/db/types";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
interface StaffFormProps {
|
||||
initialData?: Partial<Staff>;
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { Button } from "../ui/button";
|
||||
import { Delete, Edit } from "lucide-react";
|
||||
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
|
||||
const staffUpdateSchema = (
|
||||
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).partial();
|
||||
import { Staff } from "@repo/db/types";
|
||||
|
||||
interface StaffTableProps {
|
||||
staff: Staff[];
|
||||
@@ -21,7 +13,6 @@ interface StaffTableProps {
|
||||
onView: (staff: Staff) => void;
|
||||
}
|
||||
|
||||
|
||||
export function StaffTable({
|
||||
staff,
|
||||
onEdit,
|
||||
@@ -151,15 +142,13 @@ export function StaffTable({
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
|
||||
<Button
|
||||
onClick={() =>
|
||||
staff !== undefined && onDelete(staff)
|
||||
}
|
||||
onClick={() => staff !== undefined && onDelete(staff)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
aria-label="Delete Staff"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Delete/>
|
||||
<Delete />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => staff.id !== undefined && onEdit(staff)}
|
||||
@@ -170,7 +159,6 @@ export function StaffTable({
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -235,7 +223,9 @@ export function StaffTable({
|
||||
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
|
||||
}}
|
||||
className={`relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 ${
|
||||
currentPage === totalPages ? "pointer-events-none opacity-50" : ""
|
||||
currentPage === totalPages
|
||||
? "pointer-events-none opacity-50"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
Next
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { format, addDays, startOfToday, addMinutes } from "date-fns";
|
||||
import {
|
||||
parseLocalDate,
|
||||
formatLocalDate,
|
||||
} from "@/utils/dateUtils";
|
||||
import { parseLocalDate, formatLocalDate } from "@/utils/dateUtils";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { AddAppointmentModal } from "@/components/appointments/add-appointment-modal";
|
||||
@@ -19,12 +16,7 @@ import {
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { z } from "zod";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
@@ -40,35 +32,12 @@ import { Menu, Item, useContextMenu } from "react-contexify";
|
||||
import "react-contexify/ReactContexify.css";
|
||||
import { useLocation } from "wouter";
|
||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
import {
|
||||
Appointment,
|
||||
InsertAppointment,
|
||||
Patient,
|
||||
UpdateAppointment,
|
||||
} from "@repo/db/types";
|
||||
|
||||
// Define types for scheduling
|
||||
interface TimeSlot {
|
||||
@@ -287,7 +256,11 @@ export default function AppointmentsPage() {
|
||||
// Create/upsert appointment mutation
|
||||
const createAppointmentMutation = useMutation({
|
||||
mutationFn: async (appointment: InsertAppointment) => {
|
||||
const res = await apiRequest("POST", "/api/appointments/upsert", appointment);
|
||||
const res = await apiRequest(
|
||||
"POST",
|
||||
"/api/appointments/upsert",
|
||||
appointment
|
||||
);
|
||||
return await res.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -436,8 +409,8 @@ export default function AppointmentsPage() {
|
||||
const formattedDate = format(selectedDate, "yyyy-MM-dd");
|
||||
|
||||
const selectedDateAppointments = appointments.filter((appointment) => {
|
||||
const dateObj = parseLocalDate(appointment.date)
|
||||
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
|
||||
const dateObj = parseLocalDate(appointment.date);
|
||||
return formatLocalDate(dateObj) === formatLocalDate(selectedDate);
|
||||
});
|
||||
|
||||
// Process appointments for the scheduler view
|
||||
@@ -466,8 +439,8 @@ export default function AppointmentsPage() {
|
||||
...apt,
|
||||
patientName,
|
||||
staffId,
|
||||
status: apt.status ?? null,
|
||||
date: formatLocalDate(parseLocalDate(apt.date))
|
||||
status: apt.status ?? null,
|
||||
date: formatLocalDate(parseLocalDate(apt.date)),
|
||||
};
|
||||
|
||||
return processed;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { UserUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -16,41 +14,17 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Card} from "@/components/ui/card";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { CheckCircle, Torus } from "lucide-react";
|
||||
import { CheckedState } from "@radix-ui/react-checkbox";
|
||||
import LoadingScreen from "@/components/ui/LoadingScreen";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
const insertUserSchema = (
|
||||
UserUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).pick({
|
||||
username: true,
|
||||
password: true,
|
||||
});
|
||||
|
||||
const loginSchema = (insertUserSchema as unknown as z.ZodObject<any>).extend({
|
||||
rememberMe: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const registerSchema = (insertUserSchema as unknown as z.ZodObject<any>)
|
||||
.extend({
|
||||
confirmPassword: z.string().min(6, {
|
||||
message: "Password must be at least 6 characters long",
|
||||
}),
|
||||
agreeTerms: z.literal(true, {
|
||||
errorMap: () => ({
|
||||
message: "You must agree to the terms and conditions",
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.refine((data: any) => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
|
||||
type LoginFormValues = z.infer<typeof loginSchema>;
|
||||
type RegisterFormValues = z.infer<typeof registerSchema>;
|
||||
import {
|
||||
LoginFormValues,
|
||||
loginSchema,
|
||||
RegisterFormValues,
|
||||
registerSchema,
|
||||
} from "@repo/db/types";
|
||||
|
||||
export default function AuthPage() {
|
||||
const [activeTab, setActiveTab] = useState<string>("login");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useMutation} from "@tanstack/react-query";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import {
|
||||
@@ -12,13 +12,7 @@ import {
|
||||
import { ClaimForm } from "@/components/claims/claim-form";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
ClaimUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { parse } from "date-fns";
|
||||
import { z } from "zod";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useLocation } from "wouter";
|
||||
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
|
||||
@@ -27,58 +21,16 @@ import {
|
||||
clearTaskStatus,
|
||||
} from "@/redux/slices/seleniumClaimSubmitTaskSlice";
|
||||
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
||||
import { formatLocalDate} from "@/utils/dateUtils";
|
||||
import { formatLocalDate } from "@/utils/dateUtils";
|
||||
import ClaimsRecentTable from "@/components/claims/claims-recent-table";
|
||||
import ClaimsOfPatientModal from "@/components/claims/claims-of-patient-table";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
type Claim = z.infer<typeof ClaimUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
import {
|
||||
Claim,
|
||||
InsertAppointment,
|
||||
InsertPatient,
|
||||
UpdateAppointment,
|
||||
UpdatePatient,
|
||||
} from "@repo/db/types";
|
||||
|
||||
export default function ClaimsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
@@ -27,42 +27,13 @@ import {
|
||||
import { Link } from "wouter";
|
||||
import { z } from "zod";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
import {
|
||||
Appointment,
|
||||
InsertAppointment,
|
||||
InsertPatient,
|
||||
Patient,
|
||||
UpdateAppointment,
|
||||
} from "@repo/db/types";
|
||||
|
||||
// Type for the ref to access modal methods
|
||||
type AddPatientModalRef = {
|
||||
@@ -156,7 +127,11 @@ export default function Dashboard() {
|
||||
// Create/upsert appointment mutation
|
||||
const createAppointmentMutation = useMutation({
|
||||
mutationFn: async (appointment: InsertAppointment) => {
|
||||
const res = await apiRequest("POST", "/api/appointments/upsert", appointment);
|
||||
const res = await apiRequest(
|
||||
"POST",
|
||||
"/api/appointments/upsert",
|
||||
appointment
|
||||
);
|
||||
return await res.json();
|
||||
},
|
||||
onSuccess: () => {
|
||||
|
||||
@@ -11,26 +11,11 @@ import { Button } from "@/components/ui/button";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { Eye, Trash, Download, FolderOpen } from "lucide-react";
|
||||
import {
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
PdfFileUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
import { z } from "zod";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const PdfFileSchema =
|
||||
PdfFileUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>;
|
||||
type PdfFile = z.infer<typeof PdfFileSchema>;
|
||||
import { Patient, PdfFile } from "@repo/db/types";
|
||||
|
||||
export default function DocumentsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
@@ -44,11 +29,10 @@ export default function DocumentsPage() {
|
||||
const toggleMobileMenu = () => setIsMobileMenuOpen((prev) => !prev);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedGroupId(null);
|
||||
setFileBlobUrl(null);
|
||||
setSelectedPdfId(null);
|
||||
}, [selectedPatient]);
|
||||
|
||||
setSelectedGroupId(null);
|
||||
setFileBlobUrl(null);
|
||||
setSelectedPdfId(null);
|
||||
}, [selectedPatient]);
|
||||
|
||||
const { data: groups = [] } = useQuery({
|
||||
queryKey: ["groups", selectedPatient?.id],
|
||||
@@ -99,7 +83,7 @@ export default function DocumentsPage() {
|
||||
|
||||
const handleConfirmDeletePdf = () => {
|
||||
if (currentPdf) {
|
||||
deletePdfMutation.mutate(currentPdf.id);
|
||||
deletePdfMutation.mutate(Number(currentPdf.id));
|
||||
} else {
|
||||
toast({
|
||||
title: "Error",
|
||||
|
||||
@@ -13,10 +13,8 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { CalendarIcon, CheckCircle, LoaderCircleIcon } from "lucide-react";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { z } from "zod";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
import { format } from "date-fns";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
@@ -34,22 +32,7 @@ import {
|
||||
} from "@/redux/slices/seleniumEligibilityCheckTaskSlice";
|
||||
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
import { InsertPatient, Patient } from "@repo/db/types";
|
||||
|
||||
export default function InsuranceEligibilityPage() {
|
||||
const { user } = useAuth();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useRef } from "react";
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { useState, useRef } from "react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TopAppBar } from "@/components/layout/top-app-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { PatientTable } from "@/components/patients/patient-table";
|
||||
@@ -15,28 +15,11 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { PatientUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { z } from "zod";
|
||||
import useExtractPdfData from "@/hooks/use-extractPdfData";
|
||||
import { useLocation } from "wouter";
|
||||
|
||||
const PatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
appointments: true,
|
||||
});
|
||||
type Patient = z.infer<typeof PatientSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
import { InsertPatient, Patient } from "@repo/db/types";
|
||||
|
||||
// Type for the ref to access modal methods
|
||||
type AddPatientModalRef = {
|
||||
@@ -105,7 +88,6 @@ export default function PatientsPage() {
|
||||
|
||||
const isLoading = addPatientMutation.isPending;
|
||||
|
||||
|
||||
// File upload handling
|
||||
const handleFileUpload = (file: File) => {
|
||||
setIsUploading(true);
|
||||
|
||||
@@ -13,15 +13,9 @@ import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
CreditCard,
|
||||
Clock,
|
||||
CheckCircle,
|
||||
AlertCircle,
|
||||
DollarSign,
|
||||
Receipt,
|
||||
Plus,
|
||||
ArrowDown,
|
||||
ReceiptText,
|
||||
Upload,
|
||||
Image,
|
||||
X,
|
||||
@@ -53,56 +47,7 @@ import {
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import PaymentsRecentTable from "@/components/payments/payments-recent-table";
|
||||
|
||||
import {
|
||||
AppointmentUncheckedCreateInputObjectSchema,
|
||||
PatientUncheckedCreateInputObjectSchema,
|
||||
} from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
|
||||
//creating types out of schema auto generated.
|
||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertAppointment = z.infer<typeof insertAppointmentSchema>;
|
||||
|
||||
const updateAppointmentSchema = (
|
||||
AppointmentUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
})
|
||||
.partial();
|
||||
type UpdateAppointment = z.infer<typeof updateAppointmentSchema>;
|
||||
|
||||
//patient types
|
||||
type Patient = z.infer<typeof PatientUncheckedCreateInputObjectSchema>;
|
||||
|
||||
const insertPatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
type InsertPatient = z.infer<typeof insertPatientSchema>;
|
||||
|
||||
const updatePatientSchema = (
|
||||
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
|
||||
)
|
||||
.omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
userId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
||||
import { Appointment, Patient } from "@repo/db/types";
|
||||
|
||||
export default function PaymentsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
@@ -6,10 +6,9 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
// import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import { Patient, Appointment } from "@repo/db/shared/schemas";
|
||||
import { Plus, ClipboardCheck, Clock, CheckCircle, AlertCircle } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { Appointment, Patient } from "@repo/db/types";
|
||||
|
||||
export default function PreAuthorizationsPage() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
@@ -138,7 +137,7 @@ export default function PreAuthorizationsPage() {
|
||||
key={patient.id}
|
||||
className="py-4 flex items-center justify-between cursor-pointer hover:bg-gray-50"
|
||||
onClick={() => {
|
||||
setSelectedPatient(patient.id);
|
||||
setSelectedPatient(Number(patient.id));
|
||||
handleNewPreAuth(
|
||||
patient.id,
|
||||
dentalProcedures[Math.floor(Math.random() * 3)].name
|
||||
|
||||
@@ -5,16 +5,13 @@ import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { StaffTable } from "@/components/staffs/staff-table";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||
import { z } from "zod";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { StaffForm } from "@/components/staffs/staff-form";
|
||||
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
|
||||
import { CredentialTable } from "@/components/settings/insuranceCredTable";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { Staff } from "@repo/db/types";
|
||||
|
||||
// Correctly infer Staff type from zod schema
|
||||
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { toast } = useToast();
|
||||
|
||||
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?
|
||||
toothNumber String?
|
||||
toothSurface String?
|
||||
billedAmount Float
|
||||
totalBilled Decimal @db.Decimal(10, 2)
|
||||
totalPaid 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)
|
||||
@@ -212,9 +212,9 @@ model Payment {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
|
||||
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
|
||||
updatedBy User? @relation("PaymentUpdatedBy", fields: [updatedById], references: [id])
|
||||
claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
|
||||
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
|
||||
updatedBy User? @relation("PaymentUpdatedBy", fields: [updatedById], references: [id])
|
||||
serviceLineTransactions ServiceLineTransaction[]
|
||||
|
||||
@@index([id])
|
||||
|
||||
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 "./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;
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
paymentId: number;
|
||||
amount: number;
|
||||
method: PaymentMethod;
|
||||
payerName?: string;
|
||||
notes?: string;
|
||||
receivedDate: Date;
|
||||
status: PaymentStatus;
|
||||
serviceLineTransactions: {
|
||||
serviceLineId: number;
|
||||
transactionId?: string;
|
||||
paidAmount: number;
|
||||
adjustedAmount: number;
|
||||
adjustedAmount?: number;
|
||||
method: PaymentMethod;
|
||||
receivedDate: Date;
|
||||
payerName?: 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