Update shift creation to use a new form schema and improve validation

Refactor shift creation endpoint to use `insertShiftFormSchema` for validation and data transformation, and update client-side components to handle datetime strings.

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/cpTvSfP
This commit is contained in:
marco370 2025-10-11 10:31:19 +00:00
parent 133be08785
commit 177ad892f0
4 changed files with 50 additions and 22 deletions

View File

@ -22,6 +22,10 @@ externalPort = 3001
localPort = 41343
externalPort = 3000
[[ports]]
localPort = 45115
externalPort = 3002
[env]
PORT = "5000"

View File

@ -8,7 +8,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { insertShiftSchema } from "@shared/schema";
import { insertShiftFormSchema } from "@shared/schema";
import { Plus, Calendar, MapPin, Users, Clock } from "lucide-react";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useToast } from "@/hooks/use-toast";
@ -29,16 +29,13 @@ export default function Shifts() {
queryKey: ["/api/sites"],
});
const form = useForm<InsertShift>({
resolver: zodResolver(insertShiftSchema.extend({
startTime: insertShiftSchema.shape.startTime,
endTime: insertShiftSchema.shape.endTime,
})),
const form = useForm({
resolver: zodResolver(insertShiftFormSchema),
defaultValues: {
siteId: "",
startTime: new Date(),
endTime: new Date(),
status: "planned",
startTime: "",
endTime: "",
status: "planned" as const,
},
});
@ -65,13 +62,7 @@ export default function Shifts() {
});
const onSubmit = (data: InsertShift) => {
// Ensure dates are Date objects, not strings
const shiftData = {
...data,
startTime: data.startTime instanceof Date ? data.startTime : new Date(data.startTime),
endTime: data.endTime instanceof Date ? data.endTime : new Date(data.endTime),
};
createMutation.mutate(shiftData);
createMutation.mutate(data);
};
const getStatusLabel = (status: string) => {
@ -155,8 +146,8 @@ export default function Shifts() {
<input
type="datetime-local"
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm"
value={field.value ? format(new Date(field.value), "yyyy-MM-dd'T'HH:mm") : ""}
onChange={(e) => field.onChange(new Date(e.target.value))}
value={field.value}
onChange={(e) => field.onChange(e.target.value)}
data-testid="input-start-time"
/>
</FormControl>
@ -175,8 +166,8 @@ export default function Shifts() {
<input
type="datetime-local"
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm"
value={field.value ? format(new Date(field.value), "yyyy-MM-dd'T'HH:mm") : ""}
onChange={(e) => field.onChange(new Date(e.target.value))}
value={field.value}
onChange={(e) => field.onChange(e.target.value)}
data-testid="input-end-time"
/>
</FormControl>

View File

@ -3,7 +3,7 @@ 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 } from "@shared/schema";
import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema } from "@shared/schema";
import { eq } from "drizzle-orm";
import { differenceInDays } from "date-fns";
@ -197,7 +197,28 @@ export async function registerRoutes(app: Express): Promise<Server> {
app.post("/api/shifts", isAuthenticated, async (req, res) => {
try {
const shift = await storage.createShift(req.body);
// 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);

View File

@ -262,6 +262,18 @@ export const insertShiftSchema = createInsertSchema(shifts).omit({
updatedAt: true,
});
// Form schema that accepts datetime strings and transforms to Date
export const insertShiftFormSchema = z.object({
siteId: z.string().min(1, "Sito obbligatorio"),
startTime: z.string().min(1, "Data inizio obbligatoria").refine((val) => !isNaN(new Date(val).getTime()), {
message: "Data inizio non valida",
}).transform((val) => new Date(val)),
endTime: z.string().min(1, "Data fine obbligatoria").refine((val) => !isNaN(new Date(val).getTime()), {
message: "Data fine non valida",
}).transform((val) => new Date(val)),
status: z.enum(["planned", "active", "completed", "cancelled"]).default("planned"),
});
export const insertShiftAssignmentSchema = createInsertSchema(shiftAssignments).omit({
id: true,
assignedAt: true,