diff --git a/.replit b/.replit
index 90b1d94..97e4596 100644
--- a/.replit
+++ b/.replit
@@ -19,6 +19,10 @@ externalPort = 80
localPort = 33035
externalPort = 3001
+[[ports]]
+localPort = 36359
+externalPort = 5173
+
[[ports]]
localPort = 41343
externalPort = 3000
diff --git a/client/src/pages/general-planning.tsx b/client/src/pages/general-planning.tsx
index 68941fe..6a9d585 100644
--- a/client/src/pages/general-planning.tsx
+++ b/client/src/pages/general-planning.tsx
@@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
-import { ChevronLeft, ChevronRight, Calendar, MapPin, Users, AlertTriangle, Car, Edit, CheckCircle2, Plus } from "lucide-react";
+import { ChevronLeft, ChevronRight, Calendar, MapPin, Users, AlertTriangle, Car, Edit, CheckCircle2, Plus, Trash2, Clock } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import {
@@ -24,10 +24,13 @@ import { useToast } from "@/hooks/use-toast";
import type { GuardAvailability } from "@shared/schema";
interface GuardWithHours {
+ assignmentId: string;
guardId: string;
guardName: string;
badgeNumber: string;
hours: number;
+ plannedStartTime: string;
+ plannedEndTime: string;
}
interface Vehicle {
@@ -68,6 +71,18 @@ interface GeneralPlanningResponse {
};
}
+// Helper per formattare orario in formato italiano 24h (HH:MM)
+const formatTime = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleTimeString("it-IT", { hour: "2-digit", minute: "2-digit", hour12: false });
+};
+
+// Helper per formattare data in formato italiano (gg/mm/aaaa)
+const formatDateIT = (dateString: string) => {
+ const date = new Date(dateString);
+ return date.toLocaleDateString("it-IT");
+};
+
export default function GeneralPlanning() {
const [, navigate] = useLocation();
const { toast } = useToast();
@@ -124,6 +139,29 @@ export default function GeneralPlanning() {
enabled: !!selectedCell, // Query attiva solo se dialog è aperto
});
+ // Mutation per eliminare assegnazione guardia
+ const deleteAssignmentMutation = useMutation({
+ mutationFn: async (assignmentId: string) => {
+ return apiRequest("DELETE", `/api/shift-assignments/${assignmentId}`, undefined);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["/api/general-planning"] });
+ queryClient.invalidateQueries({ queryKey: ["/api/guards/availability"] });
+
+ toast({
+ title: "Guardia rimossa",
+ description: "L'assegnazione è stata eliminata con successo",
+ });
+ },
+ onError: (error: any) => {
+ toast({
+ title: "Errore",
+ description: "Impossibile eliminare l'assegnazione",
+ variant: "destructive",
+ });
+ },
+ });
+
// Mutation per assegnare guardia con orari (anche multi-giorno)
const assignGuardMutation = useMutation({
mutationFn: async (data: { siteId: string; date: string; guardId: string; startTime: string; durationHours: number; consecutiveDays: number }) => {
@@ -599,11 +637,34 @@ export default function GeneralPlanning() {
Guardie già assegnate per questa data:
-
+
{selectedCell.data.guards.map((guard, idx) => (
-
-
{guard.guardName} #{guard.badgeNumber}
-
{guard.hours}h
+
+
+
+ {guard.guardName}
+ #{guard.badgeNumber}
+
+
+
+ {formatTime(guard.plannedStartTime)} - {formatTime(guard.plannedEndTime)}
+ ({guard.hours}h)
+
+
+
))}
diff --git a/server/routes.ts b/server/routes.ts
index debfd18..bcc19b1 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -963,17 +963,20 @@ export async function registerRoutes(app: Express): Promise
{
dayShifts.some((ds: any) => ds.shift.id === a.shift.id)
);
- // Calcola ore per ogni guardia
+ // Calcola ore per ogni guardia con orari e assignmentId
const guardsWithHours = dayAssignments.map((a: any) => {
- const shiftStart = new Date(a.shift.startTime);
- const shiftEnd = new Date(a.shift.endTime);
- const hours = differenceInHours(shiftEnd, shiftStart);
+ const plannedStart = new Date(a.assignment.plannedStartTime);
+ const plannedEnd = new Date(a.assignment.plannedEndTime);
+ const hours = differenceInHours(plannedEnd, plannedStart);
return {
+ assignmentId: a.assignment.id,
guardId: a.guard.id,
guardName: a.guard.fullName,
badgeNumber: a.guard.badgeNumber,
hours,
+ plannedStartTime: a.assignment.plannedStartTime,
+ plannedEndTime: a.assignment.plannedEndTime,
};
});
@@ -1177,6 +1180,35 @@ export async function registerRoutes(app: Express): Promise {
}
});
+ // Delete a shift assignment
+ app.delete("/api/shift-assignments/:assignmentId", isAuthenticated, async (req, res) => {
+ try {
+ const { assignmentId } = req.params;
+
+ if (!assignmentId) {
+ return res.status(400).json({ message: "Assignment ID is required" });
+ }
+
+ // Delete the assignment
+ const deleted = await db
+ .delete(shiftAssignments)
+ .where(eq(shiftAssignments.id, assignmentId))
+ .returning();
+
+ if (deleted.length === 0) {
+ return res.status(404).json({ message: "Assignment not found" });
+ }
+
+ res.json({
+ message: "Assignment deleted successfully",
+ assignment: deleted[0]
+ });
+ } catch (error: any) {
+ console.error("Error deleting assignment:", error);
+ res.status(500).json({ message: "Failed to delete assignment", error: String(error) });
+ }
+ });
+
// Assign guard to site/date with specific time slot (supports multi-day assignments)
app.post("/api/general-planning/assign-guard", isAuthenticated, async (req, res) => {
try {
@@ -1204,7 +1236,7 @@ export async function registerRoutes(app: Express): Promise {
return res.status(404).json({ message: "Guard not found" });
}
- // Parse start date WITHOUT timezone conversion (stay in local time)
+ // Parse start date components
const [year, month, day] = date.split("-").map(Number);
if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) {
return res.status(400).json({ message: "Invalid date format. Expected YYYY-MM-DD" });
@@ -1217,10 +1249,18 @@ export async function registerRoutes(app: Express): Promise {
// Loop through each consecutive day
for (let dayOffset = 0; dayOffset < consecutiveDays; dayOffset++) {
- // Calculate date for this iteration
- const currentDate = new Date(year, month - 1, day + dayOffset);
- const shiftDate = new Date(currentDate);
- shiftDate.setHours(0, 0, 0, 0);
+ // Calculate date components for this iteration (avoid timezone issues)
+ const targetDay = day + dayOffset;
+ const baseDate = new Date(year, month - 1, 1); // First day of month
+ baseDate.setDate(targetDay); // Set to target day (handles month overflow)
+
+ // Extract actual date components after overflow handling
+ const actualYear = baseDate.getFullYear();
+ const actualMonth = baseDate.getMonth();
+ const actualDay = baseDate.getDate();
+
+ // Build dates in UTC to avoid timezone shifts
+ const shiftDate = new Date(Date.UTC(actualYear, actualMonth, actualDay, 0, 0, 0, 0));
// Check contract validity for this date
if (site.contractStartDate && site.contractEndDate) {
@@ -1233,17 +1273,13 @@ export async function registerRoutes(app: Express): Promise {
}
}
- // Calculate planned start and end times for this day
- const plannedStart = new Date(currentDate);
- plannedStart.setHours(hours, minutes, 0, 0);
- const plannedEnd = new Date(currentDate);
- plannedEnd.setHours(hours + durationHours, minutes, 0, 0);
+ // Calculate planned start and end times in UTC
+ const plannedStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours, minutes, 0, 0));
+ const plannedEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0));
- // Find or create shift for this site/date
- const dayStart = new Date(shiftDate);
- dayStart.setHours(0, 0, 0, 0);
- const dayEnd = new Date(shiftDate);
- dayEnd.setHours(23, 59, 59, 999);
+ // Find or create shift for this site/date (full day boundaries in UTC)
+ const dayStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, 0, 0, 0, 0));
+ const dayEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, 23, 59, 59, 999));
let existingShifts = await tx
.select()
@@ -1267,14 +1303,12 @@ export async function registerRoutes(app: Express): Promise {
const [startHour, startMin] = serviceStart.split(":").map(Number);
const [endHour, endMin] = serviceEnd.split(":").map(Number);
- const shiftStart = new Date(shiftDate);
- shiftStart.setHours(startHour, startMin, 0, 0);
-
- const shiftEnd = new Date(shiftDate);
- shiftEnd.setHours(endHour, endMin, 0, 0);
+ const shiftStart = new Date(Date.UTC(actualYear, actualMonth, actualDay, startHour, startMin, 0, 0));
+ let shiftEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay, endHour, endMin, 0, 0));
+ // If end time is before/equal to start time, shift extends to next day
if (shiftEnd <= shiftStart) {
- shiftEnd.setDate(shiftEnd.getDate() + 1);
+ shiftEnd = new Date(Date.UTC(actualYear, actualMonth, actualDay + 1, endHour, endMin, 0, 0));
}
[shift] = await tx.insert(shifts).values({