Compare commits
4 Commits
9bc4ed03d8
...
18e219e118
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18e219e118 | ||
|
|
b4ca7d594e | ||
|
|
ce1ba6ef33 | ||
|
|
1dcb20400f |
BIN
after-reload.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
@ -7,6 +7,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
|
|||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { insertGuardSchema, insertCertificationSchema } from "@shared/schema";
|
import { insertGuardSchema, insertCertificationSchema } from "@shared/schema";
|
||||||
@ -31,8 +32,12 @@ export default function Guards() {
|
|||||||
const form = useForm<InsertGuard>({
|
const form = useForm<InsertGuard>({
|
||||||
resolver: zodResolver(insertGuardSchema),
|
resolver: zodResolver(insertGuardSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
badgeNumber: "",
|
badgeNumber: "",
|
||||||
phoneNumber: "",
|
phoneNumber: "",
|
||||||
|
location: "roccapiemonte",
|
||||||
isArmed: false,
|
isArmed: false,
|
||||||
hasFireSafety: false,
|
hasFireSafety: false,
|
||||||
hasFirstAid: false,
|
hasFirstAid: false,
|
||||||
@ -44,8 +49,12 @@ export default function Guards() {
|
|||||||
const editForm = useForm<InsertGuard>({
|
const editForm = useForm<InsertGuard>({
|
||||||
resolver: zodResolver(insertGuardSchema),
|
resolver: zodResolver(insertGuardSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
badgeNumber: "",
|
badgeNumber: "",
|
||||||
phoneNumber: "",
|
phoneNumber: "",
|
||||||
|
location: "roccapiemonte",
|
||||||
isArmed: false,
|
isArmed: false,
|
||||||
hasFireSafety: false,
|
hasFireSafety: false,
|
||||||
hasFirstAid: false,
|
hasFirstAid: false,
|
||||||
@ -111,8 +120,12 @@ export default function Guards() {
|
|||||||
const openEditDialog = (guard: GuardWithCertifications) => {
|
const openEditDialog = (guard: GuardWithCertifications) => {
|
||||||
setEditingGuard(guard);
|
setEditingGuard(guard);
|
||||||
editForm.reset({
|
editForm.reset({
|
||||||
|
firstName: guard.firstName || "",
|
||||||
|
lastName: guard.lastName || "",
|
||||||
|
email: guard.email || "",
|
||||||
badgeNumber: guard.badgeNumber,
|
badgeNumber: guard.badgeNumber,
|
||||||
phoneNumber: guard.phoneNumber || "",
|
phoneNumber: guard.phoneNumber || "",
|
||||||
|
location: guard.location || "roccapiemonte",
|
||||||
isArmed: guard.isArmed,
|
isArmed: guard.isArmed,
|
||||||
hasFireSafety: guard.hasFireSafety,
|
hasFireSafety: guard.hasFireSafety,
|
||||||
hasFirstAid: guard.hasFirstAid,
|
hasFirstAid: guard.hasFirstAid,
|
||||||
@ -146,6 +159,50 @@ export default function Guards() {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nome</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Mario" {...field} data-testid="input-first-name" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Cognome</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Rossi" {...field} data-testid="input-last-name" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="mario.rossi@esempio.it" type="email" {...field} value={field.value || ""} data-testid="input-email" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="badgeNumber"
|
name="badgeNumber"
|
||||||
@ -165,7 +222,7 @@ export default function Guards() {
|
|||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Telefono</FormLabel>
|
<FormLabel>Cellulare</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="+39 123 456 7890" {...field} value={field.value || ""} data-testid="input-phone" />
|
<Input placeholder="+39 123 456 7890" {...field} value={field.value || ""} data-testid="input-phone" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -174,6 +231,29 @@ export default function Guards() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="location"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Sede di Appartenenza</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
|
<SelectTrigger data-testid="select-location">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
||||||
|
<SelectItem value="milano">Milano</SelectItem>
|
||||||
|
<SelectItem value="roma">Roma</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-sm font-medium">Competenze</p>
|
<p className="text-sm font-medium">Competenze</p>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
@ -262,11 +342,55 @@ export default function Guards() {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modifica Guardia</DialogTitle>
|
<DialogTitle>Modifica Guardia</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Modifica i dati della guardia {editingGuard?.user?.firstName} {editingGuard?.user?.lastName}
|
Modifica i dati della guardia {editingGuard?.firstName} {editingGuard?.lastName}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...editForm}>
|
<Form {...editForm}>
|
||||||
<form onSubmit={editForm.handleSubmit(onEditSubmit)} className="space-y-4">
|
<form onSubmit={editForm.handleSubmit(onEditSubmit)} className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={editForm.control}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Nome</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Mario" {...field} data-testid="input-edit-first-name" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={editForm.control}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Cognome</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Rossi" {...field} data-testid="input-edit-last-name" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={editForm.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="mario.rossi@esempio.it" type="email" {...field} value={field.value || ""} data-testid="input-edit-email" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={editForm.control}
|
control={editForm.control}
|
||||||
name="badgeNumber"
|
name="badgeNumber"
|
||||||
@ -286,7 +410,7 @@ export default function Guards() {
|
|||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Telefono</FormLabel>
|
<FormLabel>Cellulare</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="+39 123 456 7890" {...field} value={field.value || ""} data-testid="input-edit-phone" />
|
<Input placeholder="+39 123 456 7890" {...field} value={field.value || ""} data-testid="input-edit-phone" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -295,6 +419,29 @@ export default function Guards() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={editForm.control}
|
||||||
|
name="location"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Sede di Appartenenza</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
|
<SelectTrigger data-testid="select-edit-location">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="roccapiemonte">Roccapiemonte</SelectItem>
|
||||||
|
<SelectItem value="milano">Milano</SelectItem>
|
||||||
|
<SelectItem value="roma">Roma</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-sm font-medium">Competenze</p>
|
<p className="text-sm font-medium">Competenze</p>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
@ -391,15 +538,20 @@ export default function Guards() {
|
|||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarImage src={guard.user?.profileImageUrl || undefined} />
|
<AvatarImage src={guard.user?.profileImageUrl || undefined} />
|
||||||
<AvatarFallback>
|
<AvatarFallback>
|
||||||
{guard.user?.firstName?.[0]}{guard.user?.lastName?.[0]}
|
{guard.firstName?.[0]}{guard.lastName?.[0]}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<CardTitle className="text-lg truncate">
|
<CardTitle className="text-lg truncate">
|
||||||
{guard.user?.firstName} {guard.user?.lastName}
|
{guard.firstName} {guard.lastName}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="font-mono text-xs">
|
<CardDescription className="space-y-0.5">
|
||||||
{guard.badgeNumber}
|
<div className="font-mono text-xs">{guard.badgeNumber}</div>
|
||||||
|
{guard.email && <div className="text-xs truncate">{guard.email}</div>}
|
||||||
|
{guard.phoneNumber && <div className="text-xs">{guard.phoneNumber}</div>}
|
||||||
|
<Badge variant="outline" className="text-xs mt-1">
|
||||||
|
{guard.location === "roccapiemonte" ? "Roccapiemonte" : guard.location === "milano" ? "Milano" : "Roma"}
|
||||||
|
</Badge>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
BIN
database-backups/vigilanzaturni_v1.0.24_20251018_102519.sql.gz
Normal file
BIN
final_reopen_edit.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
guards-nav.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
no-guard-link.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
no-login-button.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
@ -122,6 +122,11 @@ export const users = pgTable("users", {
|
|||||||
export const guards = pgTable("guards", {
|
export const guards = pgTable("guards", {
|
||||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||||
userId: varchar("user_id").references(() => users.id),
|
userId: varchar("user_id").references(() => users.id),
|
||||||
|
|
||||||
|
// Anagrafica
|
||||||
|
firstName: varchar("first_name").notNull(),
|
||||||
|
lastName: varchar("last_name").notNull(),
|
||||||
|
email: varchar("email"),
|
||||||
badgeNumber: varchar("badge_number").notNull().unique(),
|
badgeNumber: varchar("badge_number").notNull().unique(),
|
||||||
phoneNumber: varchar("phone_number"),
|
phoneNumber: varchar("phone_number"),
|
||||||
location: locationEnum("location").notNull().default("roccapiemonte"), // Sede di appartenenza
|
location: locationEnum("location").notNull().default("roccapiemonte"), // Sede di appartenenza
|
||||||
@ -644,6 +649,13 @@ export const insertGuardSchema = createInsertSchema(guards).omit({
|
|||||||
id: true,
|
id: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
|
}).extend({
|
||||||
|
firstName: z.string().min(1, "Nome obbligatorio"),
|
||||||
|
lastName: z.string().min(1, "Cognome obbligatorio"),
|
||||||
|
email: z.string().email("Email non valida").optional().or(z.literal("")),
|
||||||
|
badgeNumber: z.string().min(1, "Matricola obbligatoria"),
|
||||||
|
phoneNumber: z.string().optional().or(z.literal("")),
|
||||||
|
location: z.enum(["roccapiemonte", "milano", "roma"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const insertCertificationSchema = createInsertSchema(certifications).omit({
|
export const insertCertificationSchema = createInsertSchema(certifications).omit({
|
||||||
|
|||||||
BIN
step11_toggled_switches.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
step14_after_submit.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
step15_post_submit.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
step23_after_edit_submit.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
step24_verify_after_edit.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
step25_after_close_dialog.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
step25_fetch_api_via_page.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
step25_reopen_edit_dialog.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
step4_page.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
step5_guards_nav.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
step8_add_guard_clicked.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
10
version.json
@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.23",
|
"version": "1.0.24",
|
||||||
"lastUpdate": "2025-10-18T09:20:55.191Z",
|
"lastUpdate": "2025-10-18T10:25:34.931Z",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "1.0.24",
|
||||||
|
"date": "2025-10-18",
|
||||||
|
"type": "patch",
|
||||||
|
"description": "Deployment automatico v1.0.24"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.23",
|
"version": "1.0.23",
|
||||||
"date": "2025-10-18",
|
"date": "2025-10-18",
|
||||||
|
|||||||