From 06c03be97c8c80cdf7c7ef48e440c24f587652b1 Mon Sep 17 00:00:00 2001 From: marco370 <48531002-marco370@users.noreply.replit.com> Date: Sat, 11 Oct 2025 11:09:25 +0000 Subject: [PATCH] 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 --- .replit | 4 -- replit.md | 12 ++++++ server/routes.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++++ server/storage.ts | 24 ++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/.replit b/.replit index 5fda2a5..03709f0 100644 --- a/.replit +++ b/.replit @@ -18,10 +18,6 @@ externalPort = 80 localPort = 33035 externalPort = 3001 -[[ports]] -localPort = 38805 -externalPort = 3002 - [[ports]] localPort = 41343 externalPort = 3000 diff --git a/replit.md b/replit.md index 5238e97..41b94ff 100644 --- a/replit.md +++ b/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 - Backend: validazione ISO strings prima di conversione a Date - 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) - Tutti i componenti testabili con data-testid attributes diff --git a/server/routes.ts b/server/routes.ts index 850ba1b..c9f8b9e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -76,6 +76,32 @@ export async function registerRoutes(app: Express): Promise { } }); + 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 ============= app.post("/api/certifications", isAuthenticated, async (req, res) => { try { @@ -108,6 +134,32 @@ export async function registerRoutes(app: Express): Promise { } }); + 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 ============= app.get("/api/shifts", isAuthenticated, async (req, res) => { try { @@ -236,6 +288,51 @@ export async function registerRoutes(app: Express): Promise { } }); + 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 ============= app.post("/api/shift-assignments", isAuthenticated, async (req, res) => { try { diff --git a/server/storage.ts b/server/storage.ts index 6250dc9..0b10bae 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -110,6 +110,11 @@ export class DatabaseStorage implements IStorage { return updated; } + async deleteGuard(id: string): Promise { + const [deleted] = await db.delete(guards).where(eq(guards.id, id)).returning(); + return deleted; + } + // Certification operations async getCertificationsByGuard(guardId: string): Promise { return await db @@ -158,6 +163,11 @@ export class DatabaseStorage implements IStorage { return updated; } + async deleteSite(id: string): Promise { + const [deleted] = await db.delete(sites).where(eq(sites.id, id)).returning(); + return deleted; + } + // Shift operations async getAllShifts(): Promise { return await db.select().from(shifts).orderBy(desc(shifts.startTime)); @@ -191,6 +201,20 @@ export class DatabaseStorage implements IStorage { .where(eq(shifts.id, id)); } + async updateShift(id: string, shiftData: Partial): Promise { + const [updated] = await db + .update(shifts) + .set({ ...shiftData, updatedAt: new Date() }) + .where(eq(shifts.id, id)) + .returning(); + return updated; + } + + async deleteShift(id: string): Promise { + const [deleted] = await db.delete(shifts).where(eq(shifts.id, id)).returning(); + return deleted; + } + // Shift Assignment operations async getShiftAssignments(shiftId: string): Promise { return await db