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