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 type { Request, Response } from "express";
import { storage } from "../storage";
import { z } from "zod";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).partial();
import {
StaffCreateBody,
StaffCreateInput,
staffCreateSchema,
staffUpdateSchema,
} from "@repo/db/types";
const router = Router();
@@ -17,12 +14,14 @@ router.post("/", async (req: Request, res: Response): Promise<any> => {
try {
const userId = req.user!.id; // from auth middleware
const validatedData = staffCreateSchema.parse({
...req.body,
const body = staffCreateSchema.parse(req.body) as StaffCreateBody;
const data: StaffCreateInput = {
...body,
userId,
});
const newStaff = await storage.createStaff(validatedData);
};
const newStaff = await storage.createStaff(data);
res.status(200).json(newStaff);
} catch (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 updatedStaff = await storage.updateStaff(
parsedStaffId,
validatedData
validatedData,
);
if (!updatedStaff) return res.status(404).send("Staff not found");
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);
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";
export interface IStorage {
getStaff(id: number): Promise<Staff | undefined>;
getAllStaff(): Promise<Staff[]>;
createStaff(staff: Staff): Promise<Staff>;
updateStaff(id: number, updates: Partial<Staff>): Promise<Staff | undefined>;
createStaff(staff: StaffCreateInput): Promise<Staff>;
updateStaff(
id: number,
updates: StaffUpdateInput,
): Promise<Staff | undefined>;
deleteStaff(id: number): Promise<boolean>;
countAppointmentsByStaffId(staffId: number): Promise<number>;
countClaimsByStaffId(staffId: number): Promise<number>;
@@ -19,38 +22,122 @@ export const staffStorage: IStorage = {
},
async getAllStaff(): Promise<Staff[]> {
const staff = await db.staff.findMany();
return staff;
},
async createStaff(staff: Staff): Promise<Staff> {
const createdStaff = await db.staff.create({
data: staff,
return db.staff.findMany({
orderBy: { displayOrder: "asc" },
});
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(
id: number,
updates: Partial<Staff>
updates: StaffUpdateInput,
): Promise<Staff | undefined> {
const updatedStaff = await db.staff.update({
where: { id },
data: updates,
return db.$transaction(async (tx: any) => {
const staff = await tx.staff.findUnique({ where: { id } });
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> {
try {
await db.staff.delete({ where: { id } });
return true;
} catch (error) {
console.error("Error deleting staff:", error);
return false;
}
},
return db.$transaction(async (tx: any) => {
const staff = await tx.staff.findUnique({ where: { id } });
if (!staff) 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> {
return await db.appointment.count({ where: { staffId } });
},