Implement API endpoints and UI components for managing shift assignments, including guard certifications and a new assignment removal mutation. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 99f0fce6-9386-489a-9632-1d81223cab44 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/99f0fce6-9386-489a-9632-1d81223cab44/9lvKVew
285 lines
9.7 KiB
TypeScript
285 lines
9.7 KiB
TypeScript
import type { Express } from "express";
|
|
import { createServer, type Server } from "http";
|
|
import { storage } from "./storage";
|
|
import { setupAuth, isAuthenticated } from "./replitAuth";
|
|
import { db } from "./db";
|
|
import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema } from "@shared/schema";
|
|
import { eq } from "drizzle-orm";
|
|
import { differenceInDays } from "date-fns";
|
|
|
|
export async function registerRoutes(app: Express): Promise<Server> {
|
|
// Auth middleware
|
|
await setupAuth(app);
|
|
|
|
// ============= AUTH ROUTES =============
|
|
app.get("/api/auth/user", isAuthenticated, async (req: any, res) => {
|
|
try {
|
|
const userId = req.user.claims.sub;
|
|
const user = await storage.getUser(userId);
|
|
res.json(user);
|
|
} catch (error) {
|
|
console.error("Error fetching user:", error);
|
|
res.status(500).json({ message: "Failed to fetch user" });
|
|
}
|
|
});
|
|
|
|
// ============= GUARD ROUTES =============
|
|
app.get("/api/guards", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const allGuards = await storage.getAllGuards();
|
|
|
|
// Fetch related data for each guard
|
|
const guardsWithDetails = await Promise.all(
|
|
allGuards.map(async (guard) => {
|
|
const certs = await storage.getCertificationsByGuard(guard.id);
|
|
const user = guard.userId ? await storage.getUser(guard.userId) : undefined;
|
|
|
|
// Update certification status based on expiry date
|
|
for (const cert of certs) {
|
|
const daysUntilExpiry = differenceInDays(new Date(cert.expiryDate), new Date());
|
|
let newStatus: "valid" | "expiring_soon" | "expired" = "valid";
|
|
|
|
if (daysUntilExpiry < 0) {
|
|
newStatus = "expired";
|
|
} else if (daysUntilExpiry <= 30) {
|
|
newStatus = "expiring_soon";
|
|
}
|
|
|
|
if (cert.status !== newStatus) {
|
|
await storage.updateCertificationStatus(cert.id, newStatus);
|
|
cert.status = newStatus;
|
|
}
|
|
}
|
|
|
|
return {
|
|
...guard,
|
|
certifications: certs,
|
|
user,
|
|
};
|
|
})
|
|
);
|
|
|
|
res.json(guardsWithDetails);
|
|
} catch (error) {
|
|
console.error("Error fetching guards:", error);
|
|
res.status(500).json({ message: "Failed to fetch guards" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/guards", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const guard = await storage.createGuard(req.body);
|
|
res.json(guard);
|
|
} catch (error) {
|
|
console.error("Error creating guard:", error);
|
|
res.status(500).json({ message: "Failed to create guard" });
|
|
}
|
|
});
|
|
|
|
// ============= CERTIFICATION ROUTES =============
|
|
app.post("/api/certifications", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const cert = await storage.createCertification(req.body);
|
|
res.json(cert);
|
|
} catch (error) {
|
|
console.error("Error creating certification:", error);
|
|
res.status(500).json({ message: "Failed to create certification" });
|
|
}
|
|
});
|
|
|
|
// ============= SITE ROUTES =============
|
|
app.get("/api/sites", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const allSites = await storage.getAllSites();
|
|
res.json(allSites);
|
|
} catch (error) {
|
|
console.error("Error fetching sites:", error);
|
|
res.status(500).json({ message: "Failed to fetch sites" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/sites", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const site = await storage.createSite(req.body);
|
|
res.json(site);
|
|
} catch (error) {
|
|
console.error("Error creating site:", error);
|
|
res.status(500).json({ message: "Failed to create site" });
|
|
}
|
|
});
|
|
|
|
// ============= SHIFT ROUTES =============
|
|
app.get("/api/shifts", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const allShifts = await storage.getAllShifts();
|
|
|
|
// Fetch related data for each shift
|
|
const shiftsWithDetails = await Promise.all(
|
|
allShifts.map(async (shift) => {
|
|
const site = await storage.getSite(shift.siteId);
|
|
const assignments = await storage.getShiftAssignments(shift.id);
|
|
|
|
// Fetch guard details for each assignment
|
|
const assignmentsWithGuards = await Promise.all(
|
|
assignments.map(async (assignment) => {
|
|
const guard = await storage.getGuard(assignment.guardId);
|
|
const certs = guard ? await storage.getCertificationsByGuard(guard.id) : [];
|
|
const user = guard?.userId ? await storage.getUser(guard.userId) : undefined;
|
|
|
|
return {
|
|
...assignment,
|
|
guard: guard ? {
|
|
...guard,
|
|
certifications: certs,
|
|
user,
|
|
} : null,
|
|
};
|
|
})
|
|
);
|
|
|
|
return {
|
|
...shift,
|
|
site: site!,
|
|
assignments: assignmentsWithGuards.filter(a => a.guard !== null),
|
|
};
|
|
})
|
|
);
|
|
|
|
res.json(shiftsWithDetails);
|
|
} catch (error) {
|
|
console.error("Error fetching shifts:", error);
|
|
res.status(500).json({ message: "Failed to fetch shifts" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/shifts/active", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const activeShifts = await storage.getActiveShifts();
|
|
|
|
// Fetch related data for each shift
|
|
const shiftsWithDetails = await Promise.all(
|
|
activeShifts.map(async (shift) => {
|
|
const site = await storage.getSite(shift.siteId);
|
|
const assignments = await storage.getShiftAssignments(shift.id);
|
|
|
|
// Fetch guard details for each assignment
|
|
const assignmentsWithGuards = await Promise.all(
|
|
assignments.map(async (assignment) => {
|
|
const guard = await storage.getGuard(assignment.guardId);
|
|
const certs = guard ? await storage.getCertificationsByGuard(guard.id) : [];
|
|
const user = guard?.userId ? await storage.getUser(guard.userId) : undefined;
|
|
|
|
return {
|
|
...assignment,
|
|
guard: guard ? {
|
|
...guard,
|
|
certifications: certs,
|
|
user,
|
|
} : null,
|
|
};
|
|
})
|
|
);
|
|
|
|
return {
|
|
...shift,
|
|
site: site!,
|
|
assignments: assignmentsWithGuards.filter(a => a.guard !== null),
|
|
};
|
|
})
|
|
);
|
|
|
|
res.json(shiftsWithDetails);
|
|
} catch (error) {
|
|
console.error("Error fetching active shifts:", error);
|
|
res.status(500).json({ message: "Failed to fetch active shifts" });
|
|
}
|
|
});
|
|
|
|
app.post("/api/shifts", isAuthenticated, async (req, res) => {
|
|
try {
|
|
// Validate that required fields are present and dates are valid strings
|
|
if (!req.body.siteId || !req.body.startTime || !req.body.endTime) {
|
|
return res.status(400).json({ message: "Missing required fields" });
|
|
}
|
|
|
|
// Convert and validate dates
|
|
const startTime = new Date(req.body.startTime);
|
|
const endTime = new Date(req.body.endTime);
|
|
|
|
if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
|
|
return res.status(400).json({ message: "Invalid date format" });
|
|
}
|
|
|
|
// Validate and transform the request body
|
|
const validatedData = insertShiftSchema.parse({
|
|
siteId: req.body.siteId,
|
|
startTime,
|
|
endTime,
|
|
status: req.body.status || "planned",
|
|
});
|
|
|
|
const shift = await storage.createShift(validatedData);
|
|
res.json(shift);
|
|
} catch (error) {
|
|
console.error("Error creating shift:", error);
|
|
res.status(500).json({ message: "Failed to create shift" });
|
|
}
|
|
});
|
|
|
|
app.patch("/api/shifts/:id/status", isAuthenticated, async (req, res) => {
|
|
try {
|
|
await storage.updateShiftStatus(req.params.id, req.body.status);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("Error updating shift status:", error);
|
|
res.status(500).json({ message: "Failed to update shift status" });
|
|
}
|
|
});
|
|
|
|
// ============= SHIFT ASSIGNMENT ROUTES =============
|
|
app.post("/api/shift-assignments", isAuthenticated, async (req, res) => {
|
|
try {
|
|
const assignment = await storage.createShiftAssignment(req.body);
|
|
res.json(assignment);
|
|
} catch (error) {
|
|
console.error("Error creating shift assignment:", error);
|
|
res.status(500).json({ message: "Failed to create shift assignment" });
|
|
}
|
|
});
|
|
|
|
app.delete("/api/shift-assignments/:id", isAuthenticated, async (req, res) => {
|
|
try {
|
|
await storage.deleteShiftAssignment(req.params.id);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("Error deleting shift assignment:", error);
|
|
res.status(500).json({ message: "Failed to delete shift assignment" });
|
|
}
|
|
});
|
|
|
|
// ============= NOTIFICATION ROUTES =============
|
|
app.get("/api/notifications", isAuthenticated, async (req: any, res) => {
|
|
try {
|
|
const userId = req.user.claims.sub;
|
|
const userNotifications = await storage.getNotificationsByUser(userId);
|
|
res.json(userNotifications);
|
|
} catch (error) {
|
|
console.error("Error fetching notifications:", error);
|
|
res.status(500).json({ message: "Failed to fetch notifications" });
|
|
}
|
|
});
|
|
|
|
app.patch("/api/notifications/:id/read", isAuthenticated, async (req, res) => {
|
|
try {
|
|
await storage.markNotificationAsRead(req.params.id);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
console.error("Error marking notification as read:", error);
|
|
res.status(500).json({ message: "Failed to mark notification as read" });
|
|
}
|
|
});
|
|
|
|
const httpServer = createServer(app);
|
|
return httpServer;
|
|
}
|