feat(staff column order in aptmpt page)

This commit is contained in:
2026-01-28 02:25:18 +05:30
parent b8df9459ca
commit 4594a264a1
7 changed files with 244 additions and 84 deletions

View File

@@ -1,15 +1,12 @@
import { Router } from "express"; import { Router } from "express";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
import { storage } from "../storage"; import { storage } from "../storage";
import { z } from "zod"; import {
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; StaffCreateBody,
StaffCreateInput,
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>; staffCreateSchema,
staffUpdateSchema,
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema; } from "@repo/db/types";
const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).partial();
const router = Router(); const router = Router();
@@ -17,12 +14,14 @@ router.post("/", async (req: Request, res: Response): Promise<any> => {
try { try {
const userId = req.user!.id; // from auth middleware const userId = req.user!.id; // from auth middleware
const validatedData = staffCreateSchema.parse({ const body = staffCreateSchema.parse(req.body) as StaffCreateBody;
...req.body,
const data: StaffCreateInput = {
...body,
userId, userId,
}); };
const newStaff = await storage.createStaff(validatedData); const newStaff = await storage.createStaff(data);
res.status(200).json(newStaff); res.status(200).json(newStaff);
} catch (error) { } catch (error) {
console.error("Failed to create staff:", error); console.error("Failed to create staff:", error);
@@ -52,12 +51,17 @@ router.put("/:id", async (req: Request, res: Response): Promise<any> => {
const validatedData = staffUpdateSchema.parse(req.body); const validatedData = staffUpdateSchema.parse(req.body);
const updatedStaff = await storage.updateStaff( const updatedStaff = await storage.updateStaff(
parsedStaffId, parsedStaffId,
validatedData validatedData,
); );
if (!updatedStaff) return res.status(404).send("Staff not found"); if (!updatedStaff) return res.status(404).send("Staff not found");
res.json(updatedStaff); res.json(updatedStaff);
} catch (error) { } catch (error: any) {
if (error.message?.includes("displayOrder")) {
return res.status(400).json({
message: error.message,
});
}
console.error("Failed to update staff:", error); console.error("Failed to update staff:", error);
res.status(500).send("Failed to update staff"); res.status(500).send("Failed to update staff");
} }

View File

@@ -1,11 +1,14 @@
import { Staff } from "@repo/db/types"; import { Staff, StaffCreateInput, StaffUpdateInput } from "@repo/db/types";
import { prisma as db } from "@repo/db/client"; import { prisma as db } from "@repo/db/client";
export interface IStorage { export interface IStorage {
getStaff(id: number): Promise<Staff | undefined>; getStaff(id: number): Promise<Staff | undefined>;
getAllStaff(): Promise<Staff[]>; getAllStaff(): Promise<Staff[]>;
createStaff(staff: Staff): Promise<Staff>; createStaff(staff: StaffCreateInput): Promise<Staff>;
updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>; updateStaff(
id: number,
updates: StaffUpdateInput,
): Promise<Staff | undefined>;
deleteStaff(id: number): Promise<boolean>; deleteStaff(id: number): Promise<boolean>;
countAppointmentsByStaffId(staffId: number): Promise<number>; countAppointmentsByStaffId(staffId: number): Promise<number>;
countClaimsByStaffId(staffId: number): Promise<number>; countClaimsByStaffId(staffId: number): Promise<number>;
@@ -19,38 +22,122 @@ export const staffStorage: IStorage = {
}, },
async getAllStaff(): Promise<Staff[]> { async getAllStaff(): Promise<Staff[]> {
const staff = await db.staff.findMany(); return db.staff.findMany({
return staff; orderBy: { displayOrder: "asc" },
},
async createStaff(staff: Staff): Promise<Staff> {
const createdStaff = await db.staff.create({
data: staff,
}); });
return createdStaff;
}, },
async createStaff(staff: StaffCreateInput): Promise<Staff> {
const max = await db.staff.aggregate({
where: {
userId: staff.userId,
displayOrder: { gt: 0 },
},
_max: { displayOrder: true },
});
return db.staff.create({
data: {
...staff,
displayOrder: (max._max.displayOrder ?? 0) + 1,
},
});
},
async updateStaff( async updateStaff(
id: number, id: number,
updates: Partial<Staff> updates: StaffUpdateInput,
): Promise<Staff | undefined> { ): Promise<Staff | undefined> {
const updatedStaff = await db.staff.update({ return db.$transaction(async (tx: any) => {
where: { id }, const staff = await tx.staff.findUnique({ where: { id } });
data: updates, if (!staff) return undefined;
const { userId, displayOrder: oldOrder } = staff;
const newOrder = updates.displayOrder;
if (newOrder === undefined || newOrder === oldOrder) {
return tx.staff.update({
where: { id },
data: updates,
});
}
const totalStaff = await tx.staff.count({ where: { userId } });
if (newOrder < 1 || newOrder > totalStaff) {
throw new Error(`displayOrder must be between 1 and ${totalStaff}`);
}
const occupyingStaff = await tx.staff.findFirst({
where: {
userId,
displayOrder: newOrder,
},
});
// CASE 1: staff already had a slot → SWAP
if (oldOrder && oldOrder > 0 && occupyingStaff) {
await tx.staff.update({
where: { id: occupyingStaff.id },
data: { displayOrder: oldOrder },
});
}
// CASE 2: first-time assignment (oldOrder = 0)
if ((!oldOrder || oldOrder === 0) && occupyingStaff) {
// find first free slot
const usedOrders = await tx.staff.findMany({
where: {
userId,
displayOrder: { gt: 0 },
},
select: { displayOrder: true },
orderBy: { displayOrder: "asc" },
});
const usedSet = new Set(usedOrders.map((s: any) => s.displayOrder));
let freeSlot = 1;
while (usedSet.has(freeSlot)) freeSlot++;
await tx.staff.update({
where: { id: occupyingStaff.id },
data: { displayOrder: freeSlot },
});
}
return tx.staff.update({
where: { id },
data: {
...updates,
displayOrder: newOrder,
},
});
}); });
return updatedStaff ?? undefined;
}, },
async deleteStaff(id: number): Promise<boolean> { async deleteStaff(id: number): Promise<boolean> {
try { return db.$transaction(async (tx: any) => {
await db.staff.delete({ where: { id } }); const staff = await tx.staff.findUnique({ where: { id } });
return true; if (!staff) return false;
} catch (error) {
console.error("Error deleting staff:", error);
return false;
}
},
const { userId, displayOrder } = staff;
await tx.staff.delete({ where: { id } });
// Shift left to remove gap
await tx.staff.updateMany({
where: {
userId,
displayOrder: { gt: displayOrder },
},
data: {
displayOrder: { decrement: 1 },
},
});
return true;
});
},
async countAppointmentsByStaffId(staffId: number): Promise<number> { async countAppointmentsByStaffId(staffId: number): Promise<number> {
return await db.appointment.count({ where: { staffId } }); return await db.appointment.count({ where: { staffId } });
}, },

View File

@@ -1,9 +1,9 @@
import { Staff } from "@repo/db/types"; import { Staff, StaffFormData } from "@repo/db/types";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
interface StaffFormProps { interface StaffFormProps {
initialData?: Partial<Staff>; initialData?: Partial<Staff>;
onSubmit: (data: Omit<Staff, "id" | "createdAt">) => void; onSubmit: (data: StaffFormData) => void;
onCancel: () => void; onCancel: () => void;
isLoading?: boolean; isLoading?: boolean;
} }
@@ -21,6 +21,8 @@ export function StaffForm({
const [hasTypedRole, setHasTypedRole] = useState(false); const [hasTypedRole, setHasTypedRole] = useState(false);
const [displayOrder, setDisplayOrder] = useState<number | "">("");
// Set initial values once on mount // Set initial values once on mount
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
@@ -28,6 +30,9 @@ export function StaffForm({
if (initialData.email) setEmail(initialData.email); if (initialData.email) setEmail(initialData.email);
if (initialData.role) setRole(initialData.role); if (initialData.role) setRole(initialData.role);
if (initialData.phone) setPhone(initialData.phone); if (initialData.phone) setPhone(initialData.phone);
if (initialData?.displayOrder !== undefined) {
setDisplayOrder(initialData.displayOrder);
}
} }
}, []); // run once only }, []); // run once only
@@ -43,6 +48,7 @@ export function StaffForm({
email: email.trim() || undefined, email: email.trim() || undefined,
role: role.trim(), role: role.trim(),
phone: phone.trim() || undefined, phone: phone.trim() || undefined,
displayOrder: displayOrder === "" ? undefined : displayOrder,
}); });
}; };
@@ -95,6 +101,24 @@ export function StaffForm({
/> />
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700">
Column Order
</label>
<input
type="number"
className="mt-1 block w-full border rounded p-2"
value={displayOrder}
onChange={(e) =>
setDisplayOrder(e.target.value === "" ? "" : Number(e.target.value))
}
disabled={isLoading}
/>
<p className="text-xs text-gray-500">
Lower number appears first in appointment schedule
</p>
</div>
<div> <div>
<label className="block text-sm font-medium text-gray-700">Phone</label> <label className="block text-sm font-medium text-gray-700">Phone</label>
<input <input

View File

@@ -39,6 +39,7 @@ import {
Patient, Patient,
PatientStatus, PatientStatus,
UpdateAppointment, UpdateAppointment,
Staff as DBStaff,
} from "@repo/db/types"; } from "@repo/db/types";
import { import {
Popover, Popover,
@@ -61,12 +62,9 @@ interface TimeSlot {
displayTime: string; displayTime: string;
} }
interface Staff { type StaffWithColor = DBStaff & {
id: string;
name: string;
role: "doctor" | "hygienist";
color: string; color: string;
} };
interface ScheduledAppointment { interface ScheduledAppointment {
id?: number; id?: number;
@@ -99,10 +97,10 @@ export default function AppointmentsPage() {
number | null number | null
>(null); >(null);
const [proceduresPatientId, setProceduresPatientId] = useState<number | null>( const [proceduresPatientId, setProceduresPatientId] = useState<number | null>(
null null,
); );
const [proceduresPatient, setProceduresPatient] = useState<Patient | null>( const [proceduresPatient, setProceduresPatient] = useState<Patient | null>(
null null,
); );
const [calendarOpen, setCalendarOpen] = useState(false); const [calendarOpen, setCalendarOpen] = useState(false);
@@ -117,7 +115,7 @@ export default function AppointmentsPage() {
}>({ open: false }); }>({ open: false });
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const batchTask = useAppSelector( const batchTask = useAppSelector(
(state) => state.seleniumEligibilityBatchCheckTask (state) => state.seleniumEligibilityBatchCheckTask,
); );
const [isCheckingAllElig, setIsCheckingAllElig] = useState(false); const [isCheckingAllElig, setIsCheckingAllElig] = useState(false);
const [processedAppointmentIds, setProcessedAppointmentIds] = useState< const [processedAppointmentIds, setProcessedAppointmentIds] = useState<
@@ -153,7 +151,7 @@ export default function AppointmentsPage() {
queryFn: async () => { queryFn: async () => {
const res = await apiRequest( const res = await apiRequest(
"GET", "GET",
`/api/appointments/day?date=${formattedSelectedDate}` `/api/appointments/day?date=${formattedSelectedDate}`,
); );
if (!res.ok) { if (!res.ok) {
throw new Error("Failed to load appointments for date"); throw new Error("Failed to load appointments for date");
@@ -168,7 +166,7 @@ export default function AppointmentsPage() {
const patientsFromDay = dayPayload.patients ?? []; const patientsFromDay = dayPayload.patients ?? [];
// Staff memebers // Staff memebers
const { data: staffMembersRaw = [] as Staff[] } = useQuery<Staff[]>({ const { data: staffMembersRaw = [] as DBStaff[] } = useQuery<DBStaff[]>({
queryKey: ["/api/staffs/"], queryKey: ["/api/staffs/"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/staffs/"); const res = await apiRequest("GET", "/api/staffs/");
@@ -186,11 +184,18 @@ export default function AppointmentsPage() {
"bg-orange-500", // softer warm orange "bg-orange-500", // softer warm orange
]; ];
// Assign colors cycling through the list // Assign colors cycling through the list, and order them by display order for the page column.
const staffMembers = staffMembersRaw.map((staff, index) => ({
...staff,
color: colors[index % colors.length] || "bg-gray-400", const orderedStaff = staffMembersRaw.filter(
(s): s is DBStaff & { displayOrder: number } =>
typeof s.displayOrder === "number" && s.displayOrder > 0,
);
orderedStaff.sort((a, b) => a.displayOrder - b.displayOrder);
const staffMembers: StaffWithColor[] = orderedStaff.map((staff, index) => ({
...staff,
color: colors[index % colors.length] ?? "bg-gray-400",
})); }));
// Generate time slots from 8:00 AM to 6:00 PM in 15-minute increments // Generate time slots from 8:00 AM to 6:00 PM in 15-minute increments
@@ -245,13 +250,13 @@ export default function AppointmentsPage() {
}; };
sessionStorage.setItem( sessionStorage.setItem(
"newAppointmentData", "newAppointmentData",
JSON.stringify(newAppointmentData) JSON.stringify(newAppointmentData),
); );
} catch (err) { } catch (err) {
// If sessionStorage parsing fails, overwrite with a fresh object // If sessionStorage parsing fails, overwrite with a fresh object
sessionStorage.setItem( sessionStorage.setItem(
"newAppointmentData", "newAppointmentData",
JSON.stringify({ patientId: patientId }) JSON.stringify({ patientId: patientId }),
); );
} }
@@ -272,7 +277,7 @@ export default function AppointmentsPage() {
const res = await apiRequest( const res = await apiRequest(
"POST", "POST",
"/api/appointments/upsert", "/api/appointments/upsert",
appointment appointment,
); );
return await res.json(); return await res.json();
}, },
@@ -308,7 +313,7 @@ export default function AppointmentsPage() {
const res = await apiRequest( const res = await apiRequest(
"PUT", "PUT",
`/api/appointments/${id}`, `/api/appointments/${id}`,
appointment appointment,
); );
return await res.json(); return await res.json();
}, },
@@ -359,7 +364,7 @@ export default function AppointmentsPage() {
// Handle appointment submission (create or update) // Handle appointment submission (create or update)
const handleAppointmentSubmit = ( const handleAppointmentSubmit = (
appointmentData: InsertAppointment | UpdateAppointment appointmentData: InsertAppointment | UpdateAppointment,
) => { ) => {
// Converts local date to exact UTC date with no offset issues // Converts local date to exact UTC date with no offset issues
const rawDate = parseLocalDate(appointmentData.date); const rawDate = parseLocalDate(appointmentData.date);
@@ -479,7 +484,7 @@ export default function AppointmentsPage() {
// Handle creating a new appointment at a specific time slot and for a specific staff member // Handle creating a new appointment at a specific time slot and for a specific staff member
const handleCreateAppointmentAtSlot = ( const handleCreateAppointmentAtSlot = (
timeSlot: TimeSlot, timeSlot: TimeSlot,
staffId: number staffId: number,
) => { ) => {
// Calculate end time (30 minutes after start time) // Calculate end time (30 minutes after start time)
const startHour = parseInt(timeSlot.time.split(":")[0] as string); const startHour = parseInt(timeSlot.time.split(":")[0] as string);
@@ -532,7 +537,7 @@ export default function AppointmentsPage() {
try { try {
sessionStorage.setItem( sessionStorage.setItem(
"newAppointmentData", "newAppointmentData",
JSON.stringify(mergedAppointment) JSON.stringify(mergedAppointment),
); );
} catch (e) { } catch (e) {
// ignore sessionStorage write failures // ignore sessionStorage write failures
@@ -550,7 +555,7 @@ export default function AppointmentsPage() {
const handleMoveAppointment = ( const handleMoveAppointment = (
appointmentId: number, appointmentId: number,
newTimeSlot: TimeSlot, newTimeSlot: TimeSlot,
newStaffId: number newStaffId: number,
) => { ) => {
const appointment = appointments.find((a) => a.id === appointmentId); const appointment = appointments.find((a) => a.id === appointmentId);
if (!appointment) return; if (!appointment) return;
@@ -603,7 +608,7 @@ export default function AppointmentsPage() {
staff, staff,
}: { }: {
appointment: ScheduledAppointment; appointment: ScheduledAppointment;
staff: Staff; staff: StaffWithColor;
}) { }) {
const [{ isDragging }, drag] = useDrag(() => ({ const [{ isDragging }, drag] = useDrag(() => ({
type: ItemTypes.APPOINTMENT, type: ItemTypes.APPOINTMENT,
@@ -624,7 +629,7 @@ export default function AppointmentsPage() {
// Only allow edit on click if we're not dragging // Only allow edit on click if we're not dragging
if (!isDragging) { if (!isDragging) {
const fullAppointment = appointments.find( const fullAppointment = appointments.find(
(a) => a.id === appointment.id (a) => a.id === appointment.id,
); );
if (fullAppointment) { if (fullAppointment) {
e.stopPropagation(); e.stopPropagation();
@@ -659,7 +664,7 @@ export default function AppointmentsPage() {
timeSlot: TimeSlot; timeSlot: TimeSlot;
staffId: number; staffId: number;
appointment: ScheduledAppointment | undefined; appointment: ScheduledAppointment | undefined;
staff: Staff; staff: StaffWithColor;
}) { }) {
const [{ isOver, canDrop }, drop] = useDrop(() => ({ const [{ isOver, canDrop }, drop] = useDrop(() => ({
accept: ItemTypes.APPOINTMENT, accept: ItemTypes.APPOINTMENT,
@@ -700,13 +705,13 @@ export default function AppointmentsPage() {
// appointment page — update these handlers // appointment page — update these handlers
const handleCheckEligibility = (appointmentId: number) => { const handleCheckEligibility = (appointmentId: number) => {
setLocation( setLocation(
`/insurance-status?appointmentId=${appointmentId}&action=eligibility` `/insurance-status?appointmentId=${appointmentId}&action=eligibility`,
); );
}; };
const handleCheckClaimStatus = (appointmentId: number) => { const handleCheckClaimStatus = (appointmentId: number) => {
setLocation( setLocation(
`/insurance-status?appointmentId=${appointmentId}&action=claim` `/insurance-status?appointmentId=${appointmentId}&action=claim`,
); );
}; };
@@ -720,7 +725,7 @@ export default function AppointmentsPage() {
const handleChartPlan = (appointmentId: number) => { const handleChartPlan = (appointmentId: number) => {
console.log( console.log(
`Viewing chart/treatment plan for appointment: ${appointmentId}` `Viewing chart/treatment plan for appointment: ${appointmentId}`,
); );
}; };
@@ -745,7 +750,7 @@ export default function AppointmentsPage() {
setTaskStatus({ setTaskStatus({
status: "pending", status: "pending",
message: `Checking eligibility for appointments on ${dateParam}...`, message: `Checking eligibility for appointments on ${dateParam}...`,
}) }),
); );
setIsCheckingAllElig(true); setIsCheckingAllElig(true);
@@ -755,7 +760,7 @@ export default function AppointmentsPage() {
const res = await apiRequest( const res = await apiRequest(
"POST", "POST",
`/api/insurance-status/appointments/check-all-eligibilities?date=${dateParam}`, `/api/insurance-status/appointments/check-all-eligibilities?date=${dateParam}`,
{} {},
); );
// read body for all cases so we can show per-appointment info // read body for all cases so we can show per-appointment info
@@ -773,7 +778,7 @@ export default function AppointmentsPage() {
setTaskStatus({ setTaskStatus({
status: "error", status: "error",
message: `Batch eligibility failed: ${errMsg}`, message: `Batch eligibility failed: ${errMsg}`,
}) }),
); );
toast({ toast({
title: "Batch check failed", title: "Batch check failed",
@@ -851,7 +856,7 @@ export default function AppointmentsPage() {
setTaskStatus({ setTaskStatus({
status: skippedCount > 0 ? "error" : "success", status: skippedCount > 0 ? "error" : "success",
message: `Batch processed ${results.length} appointments — success: ${successCount}, warnings: ${warningCount}, skipped: ${skippedCount}.`, message: `Batch processed ${results.length} appointments — success: ${successCount}, warnings: ${warningCount}, skipped: ${skippedCount}.`,
}) }),
); );
// also show final toast summary // also show final toast summary
@@ -866,7 +871,7 @@ export default function AppointmentsPage() {
setTaskStatus({ setTaskStatus({
status: "error", status: "error",
message: `Batch eligibility error: ${err?.message ?? String(err)}`, message: `Batch eligibility error: ${err?.message ?? String(err)}`,
}) }),
); );
toast({ toast({
title: "Batch check failed", title: "Batch check failed",
@@ -970,7 +975,7 @@ export default function AppointmentsPage() {
<Item <Item
onClick={({ props }) => { onClick={({ props }) => {
const fullAppointment = appointments.find( const fullAppointment = appointments.find(
(a) => a.id === props.appointmentId (a) => a.id === props.appointmentId,
); );
if (fullAppointment) { if (fullAppointment) {
handleEditAppointment(fullAppointment); handleEditAppointment(fullAppointment);
@@ -1148,7 +1153,7 @@ export default function AppointmentsPage() {
staffId={Number(staff.id)} staffId={Number(staff.id)}
appointment={getAppointmentAtSlot( appointment={getAppointmentAtSlot(
timeSlot, timeSlot,
Number(staff.id) Number(staff.id),
)} )}
staff={staff} staff={staff}
/> />

View File

@@ -8,7 +8,7 @@ import { StaffForm } from "@/components/staffs/staff-form";
import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog"; import { DeleteConfirmationDialog } from "@/components/ui/deleteDialog";
import { CredentialTable } from "@/components/settings/insuranceCredTable"; import { CredentialTable } from "@/components/settings/insuranceCredTable";
import { useAuth } from "@/hooks/use-auth"; import { useAuth } from "@/hooks/use-auth";
import { Staff } from "@repo/db/types"; import { Staff, StaffFormData } from "@repo/db/types";
import { NpiProviderTable } from "@/components/settings/npiProviderTable"; import { NpiProviderTable } from "@/components/settings/npiProviderTable";
export default function SettingsPage() { export default function SettingsPage() {
@@ -44,9 +44,9 @@ export default function SettingsPage() {
const addStaffMutate = useMutation< const addStaffMutate = useMutation<
Staff, // Return type Staff, // Return type
Error, // Error type Error, // Error type
Omit<Staff, "id" | "createdAt"> // Variables StaffFormData
>({ >({
mutationFn: async (newStaff: Omit<Staff, "id" | "createdAt">) => { mutationFn: async (newStaff: StaffFormData) => {
const res = await apiRequest("POST", "/api/staffs/", newStaff); const res = await apiRequest("POST", "/api/staffs/", newStaff);
if (!res.ok) { if (!res.ok) {
const errorData = await res.json().catch(() => null); const errorData = await res.json().catch(() => null);
@@ -75,7 +75,7 @@ export default function SettingsPage() {
const updateStaffMutate = useMutation< const updateStaffMutate = useMutation<
Staff, Staff,
Error, Error,
{ id: number; updatedFields: Partial<Staff> } { id: number; updatedFields: Partial<StaffFormData> }
>({ >({
mutationFn: async ({ mutationFn: async ({
id, id,
@@ -157,7 +157,7 @@ export default function SettingsPage() {
}; };
// Handle form submit for Add or Edit // Handle form submit for Add or Edit
const handleFormSubmit = (formData: Omit<Staff, "id" | "createdAt">) => { const handleFormSubmit = (formData: StaffFormData) => {
if (editingStaff) { if (editingStaff) {
// Editing existing staff // Editing existing staff
if (editingStaff.id === undefined) { if (editingStaff.id === undefined) {

View File

@@ -110,6 +110,7 @@ model Staff {
role String // e.g., "Dentist", "Hygienist", "Assistant" role String // e.g., "Dentist", "Hygienist", "Assistant"
phone String? phone String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
displayOrder Int @default(0)
user User? @relation(fields: [userId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
appointments Appointment[] appointments Appointment[]
claims Claim[] @relation("ClaimStaff") claims Claim[] @relation("ClaimStaff")
@@ -128,7 +129,6 @@ model NpiProvider {
@@index([userId]) @@index([userId])
} }
enum ProcedureSource { enum ProcedureSource {
COMBO COMBO
MANUAL MANUAL

View File

@@ -1,4 +1,44 @@
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import {z} from "zod"; import { z } from "zod";
export type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>; export type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
export const staffCreateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).omit({
id: true,
createdAt: true,
displayOrder: true,
userId: true,
});
export const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
)
.partial()
.omit({
id: true,
createdAt: true,
userId: true,
});
export type StaffFormData = {
name: string;
email?: string;
role: string;
phone?: string;
displayOrder?: number;
};
export type StaffCreateInput = z.infer<
typeof StaffUncheckedCreateInputObjectSchema
>;
export type StaffCreateBody = Omit<
StaffCreateInput,
"id" | "createdAt" | "userId" | "displayOrder"
>;
export type StaffUpdateInput = Partial<
Omit<StaffCreateInput, "id" | "createdAt" | "userId">
>;