feat(staff column order in aptmpt page)
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 } });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user