Add and update guard, site, and shift management functionality
Implement CRUD operations for guards, sites, and shifts, including PATCH and DELETE endpoints, and enhance shift assignment logic with skill validation and real-time synchronization. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 99f0fce6-9386-489a-9632-1d81223cab44 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/99f0fce6-9386-489a-9632-1d81223cab44/9lvKVew
This commit is contained in:
parent
b3d0441306
commit
06c03be97c
4
.replit
4
.replit
@ -18,10 +18,6 @@ externalPort = 80
|
|||||||
localPort = 33035
|
localPort = 33035
|
||||||
externalPort = 3001
|
externalPort = 3001
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 38805
|
|
||||||
externalPort = 3002
|
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 41343
|
localPort = 41343
|
||||||
externalPort = 3000
|
externalPort = 3000
|
||||||
|
|||||||
12
replit.md
12
replit.md
@ -193,6 +193,18 @@ All interactive elements have `data-testid` attributes for automated testing.
|
|||||||
- Frontend: form validation client-side con messaggi errore chiari
|
- Frontend: form validation client-side con messaggi errore chiari
|
||||||
- Backend: validazione ISO strings prima di conversione a Date
|
- Backend: validazione ISO strings prima di conversione a Date
|
||||||
- Test e2e passati con successo ✅
|
- Test e2e passati con successo ✅
|
||||||
|
- **Sistema assegnazione guardie ai turni** ✅:
|
||||||
|
- Dialog assegnazione con validazione skills vs requisiti sito
|
||||||
|
- Mostra competenze guardie (Armato, Patente, Primo Soccorso, Antincendio)
|
||||||
|
- Solo guardie idonee possono essere assegnate
|
||||||
|
- Add/Remove assignments con sync real-time (refetchQueries)
|
||||||
|
- DELETE /api/shift-assignments/:id implementato
|
||||||
|
- **CRUD completo (Backend)** ✅:
|
||||||
|
- PATCH/DELETE /api/guards/:id
|
||||||
|
- PATCH/DELETE /api/sites/:id
|
||||||
|
- PATCH/DELETE /api/shifts/:id
|
||||||
|
- 404 handling quando risorse non esistono
|
||||||
|
- Storage methods restituiscono entità aggiornate/eliminate
|
||||||
- Aggiunto SEO completo (title, meta description, Open Graph)
|
- Aggiunto SEO completo (title, meta description, Open Graph)
|
||||||
- Tutti i componenti testabili con data-testid attributes
|
- Tutti i componenti testabili con data-testid attributes
|
||||||
|
|
||||||
|
|||||||
@ -76,6 +76,32 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.patch("/api/guards/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updated = await storage.updateGuard(req.params.id, req.body);
|
||||||
|
if (!updated) {
|
||||||
|
return res.status(404).json({ message: "Guard not found" });
|
||||||
|
}
|
||||||
|
res.json(updated);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating guard:", error);
|
||||||
|
res.status(500).json({ message: "Failed to update guard" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/api/guards/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deleted = await storage.deleteGuard(req.params.id);
|
||||||
|
if (!deleted) {
|
||||||
|
return res.status(404).json({ message: "Guard not found" });
|
||||||
|
}
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting guard:", error);
|
||||||
|
res.status(500).json({ message: "Failed to delete guard" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============= CERTIFICATION ROUTES =============
|
// ============= CERTIFICATION ROUTES =============
|
||||||
app.post("/api/certifications", isAuthenticated, async (req, res) => {
|
app.post("/api/certifications", isAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -108,6 +134,32 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.patch("/api/sites/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const updated = await storage.updateSite(req.params.id, req.body);
|
||||||
|
if (!updated) {
|
||||||
|
return res.status(404).json({ message: "Site not found" });
|
||||||
|
}
|
||||||
|
res.json(updated);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating site:", error);
|
||||||
|
res.status(500).json({ message: "Failed to update site" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/api/sites/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deleted = await storage.deleteSite(req.params.id);
|
||||||
|
if (!deleted) {
|
||||||
|
return res.status(404).json({ message: "Site not found" });
|
||||||
|
}
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting site:", error);
|
||||||
|
res.status(500).json({ message: "Failed to delete site" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============= SHIFT ROUTES =============
|
// ============= SHIFT ROUTES =============
|
||||||
app.get("/api/shifts", isAuthenticated, async (req, res) => {
|
app.get("/api/shifts", isAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -236,6 +288,51 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.patch("/api/shifts/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { startTime: startTimeStr, endTime: endTimeStr, ...rest } = req.body;
|
||||||
|
const updateData: any = { ...rest };
|
||||||
|
|
||||||
|
if (startTimeStr) {
|
||||||
|
const startTime = new Date(startTimeStr);
|
||||||
|
if (isNaN(startTime.getTime())) {
|
||||||
|
return res.status(400).json({ message: "Invalid start time format" });
|
||||||
|
}
|
||||||
|
updateData.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endTimeStr) {
|
||||||
|
const endTime = new Date(endTimeStr);
|
||||||
|
if (isNaN(endTime.getTime())) {
|
||||||
|
return res.status(400).json({ message: "Invalid end time format" });
|
||||||
|
}
|
||||||
|
updateData.endTime = endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await storage.updateShift(req.params.id, updateData);
|
||||||
|
if (!updated) {
|
||||||
|
return res.status(404).json({ message: "Shift not found" });
|
||||||
|
}
|
||||||
|
res.json(updated);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating shift:", error);
|
||||||
|
res.status(500).json({ message: "Failed to update shift" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/api/shifts/:id", isAuthenticated, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deleted = await storage.deleteShift(req.params.id);
|
||||||
|
if (!deleted) {
|
||||||
|
return res.status(404).json({ message: "Shift not found" });
|
||||||
|
}
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting shift:", error);
|
||||||
|
res.status(500).json({ message: "Failed to delete shift" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ============= SHIFT ASSIGNMENT ROUTES =============
|
// ============= SHIFT ASSIGNMENT ROUTES =============
|
||||||
app.post("/api/shift-assignments", isAuthenticated, async (req, res) => {
|
app.post("/api/shift-assignments", isAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -110,6 +110,11 @@ export class DatabaseStorage implements IStorage {
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteGuard(id: string): Promise<Guard | undefined> {
|
||||||
|
const [deleted] = await db.delete(guards).where(eq(guards.id, id)).returning();
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
// Certification operations
|
// Certification operations
|
||||||
async getCertificationsByGuard(guardId: string): Promise<Certification[]> {
|
async getCertificationsByGuard(guardId: string): Promise<Certification[]> {
|
||||||
return await db
|
return await db
|
||||||
@ -158,6 +163,11 @@ export class DatabaseStorage implements IStorage {
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteSite(id: string): Promise<Site | undefined> {
|
||||||
|
const [deleted] = await db.delete(sites).where(eq(sites.id, id)).returning();
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
// Shift operations
|
// Shift operations
|
||||||
async getAllShifts(): Promise<Shift[]> {
|
async getAllShifts(): Promise<Shift[]> {
|
||||||
return await db.select().from(shifts).orderBy(desc(shifts.startTime));
|
return await db.select().from(shifts).orderBy(desc(shifts.startTime));
|
||||||
@ -191,6 +201,20 @@ export class DatabaseStorage implements IStorage {
|
|||||||
.where(eq(shifts.id, id));
|
.where(eq(shifts.id, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateShift(id: string, shiftData: Partial<InsertShift>): Promise<Shift | undefined> {
|
||||||
|
const [updated] = await db
|
||||||
|
.update(shifts)
|
||||||
|
.set({ ...shiftData, updatedAt: new Date() })
|
||||||
|
.where(eq(shifts.id, id))
|
||||||
|
.returning();
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteShift(id: string): Promise<Shift | undefined> {
|
||||||
|
const [deleted] = await db.delete(shifts).where(eq(shifts.id, id)).returning();
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
// Shift Assignment operations
|
// Shift Assignment operations
|
||||||
async getShiftAssignments(shiftId: string): Promise<ShiftAssignment[]> {
|
async getShiftAssignments(shiftId: string): Promise<ShiftAssignment[]> {
|
||||||
return await db
|
return await db
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user