Initial commit adds core UI components, including layout elements, form controls, and navigation elements, along with the main application structure and routing. 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/nGJAldO
316 lines
9.2 KiB
TypeScript
316 lines
9.2 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { relations } from "drizzle-orm";
|
|
import {
|
|
pgTable,
|
|
varchar,
|
|
text,
|
|
timestamp,
|
|
index,
|
|
jsonb,
|
|
boolean,
|
|
integer,
|
|
date,
|
|
pgEnum,
|
|
} from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
// ============= ENUMS =============
|
|
|
|
export const userRoleEnum = pgEnum("user_role", [
|
|
"admin",
|
|
"coordinator",
|
|
"guard",
|
|
"client",
|
|
]);
|
|
|
|
export const shiftTypeEnum = pgEnum("shift_type", [
|
|
"fixed_post", // Presidio fisso
|
|
"patrol", // Pattugliamento/ronde
|
|
"night_inspection", // Ispettorato notturno
|
|
"quick_response", // Pronto intervento
|
|
]);
|
|
|
|
export const shiftStatusEnum = pgEnum("shift_status", [
|
|
"planned",
|
|
"active",
|
|
"completed",
|
|
"cancelled",
|
|
]);
|
|
|
|
export const certificationStatusEnum = pgEnum("certification_status", [
|
|
"valid",
|
|
"expiring_soon", // < 30 days
|
|
"expired",
|
|
]);
|
|
|
|
// ============= SESSION & AUTH TABLES (Replit Auth) =============
|
|
|
|
// Session storage table - mandatory for Replit Auth
|
|
export const sessions = pgTable(
|
|
"sessions",
|
|
{
|
|
sid: varchar("sid").primaryKey(),
|
|
sess: jsonb("sess").notNull(),
|
|
expire: timestamp("expire").notNull(),
|
|
},
|
|
(table) => [index("IDX_session_expire").on(table.expire)]
|
|
);
|
|
|
|
// User storage table - mandatory for Replit Auth
|
|
export const users = pgTable("users", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
email: varchar("email").unique(),
|
|
firstName: varchar("first_name"),
|
|
lastName: varchar("last_name"),
|
|
profileImageUrl: varchar("profile_image_url"),
|
|
role: userRoleEnum("role").notNull().default("guard"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= GUARDS & CERTIFICATIONS =============
|
|
|
|
export const guards = pgTable("guards", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
badgeNumber: varchar("badge_number").notNull().unique(),
|
|
phoneNumber: varchar("phone_number"),
|
|
|
|
// Skills
|
|
isArmed: boolean("is_armed").default(false),
|
|
hasFireSafety: boolean("has_fire_safety").default(false),
|
|
hasFirstAid: boolean("has_first_aid").default(false),
|
|
hasDriverLicense: boolean("has_driver_license").default(false),
|
|
languages: text("languages").array(),
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
export const certifications = pgTable("certifications", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
type: varchar("type").notNull(), // porto_armi, medical_exam, training_course
|
|
name: varchar("name").notNull(),
|
|
issueDate: date("issue_date").notNull(),
|
|
expiryDate: date("expiry_date").notNull(),
|
|
status: certificationStatusEnum("status").notNull().default("valid"),
|
|
documentUrl: varchar("document_url"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// ============= SITES & CONTRACTS =============
|
|
|
|
export const sites = pgTable("sites", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: varchar("name").notNull(),
|
|
address: varchar("address").notNull(),
|
|
clientId: varchar("client_id").references(() => users.id),
|
|
|
|
// Service requirements
|
|
shiftType: shiftTypeEnum("shift_type").notNull(),
|
|
minGuards: integer("min_guards").notNull().default(1),
|
|
requiresArmed: boolean("requires_armed").default(false),
|
|
requiresDriverLicense: boolean("requires_driver_license").default(false),
|
|
|
|
// Coordinates for geofencing (future use)
|
|
latitude: varchar("latitude"),
|
|
longitude: varchar("longitude"),
|
|
|
|
isActive: boolean("is_active").default(true),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
// ============= SHIFTS & ASSIGNMENTS =============
|
|
|
|
export const shifts = pgTable("shifts", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
siteId: varchar("site_id").notNull().references(() => sites.id, { onDelete: "cascade" }),
|
|
|
|
startTime: timestamp("start_time").notNull(),
|
|
endTime: timestamp("end_time").notNull(),
|
|
status: shiftStatusEnum("status").notNull().default("planned"),
|
|
|
|
notes: text("notes"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
export const shiftAssignments = pgTable("shift_assignments", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
shiftId: varchar("shift_id").notNull().references(() => shifts.id, { onDelete: "cascade" }),
|
|
guardId: varchar("guard_id").notNull().references(() => guards.id, { onDelete: "cascade" }),
|
|
|
|
assignedAt: timestamp("assigned_at").defaultNow(),
|
|
confirmedAt: timestamp("confirmed_at"),
|
|
|
|
// Actual check-in/out times
|
|
checkInTime: timestamp("check_in_time"),
|
|
checkOutTime: timestamp("check_out_time"),
|
|
});
|
|
|
|
// ============= NOTIFICATIONS =============
|
|
|
|
export const notifications = pgTable("notifications", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
|
|
title: varchar("title").notNull(),
|
|
message: text("message").notNull(),
|
|
type: varchar("type").notNull(), // shift_assigned, certification_expiring, shift_reminder
|
|
|
|
isRead: boolean("is_read").default(false),
|
|
relatedEntityId: varchar("related_entity_id"), // shift_id, certification_id, etc.
|
|
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// ============= RELATIONS =============
|
|
|
|
export const usersRelations = relations(users, ({ one, many }) => ({
|
|
guard: one(guards, {
|
|
fields: [users.id],
|
|
references: [guards.userId],
|
|
}),
|
|
managedSites: many(sites),
|
|
notifications: many(notifications),
|
|
}));
|
|
|
|
export const guardsRelations = relations(guards, ({ one, many }) => ({
|
|
user: one(users, {
|
|
fields: [guards.userId],
|
|
references: [users.id],
|
|
}),
|
|
certifications: many(certifications),
|
|
shiftAssignments: many(shiftAssignments),
|
|
}));
|
|
|
|
export const certificationsRelations = relations(certifications, ({ one }) => ({
|
|
guard: one(guards, {
|
|
fields: [certifications.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const sitesRelations = relations(sites, ({ one, many }) => ({
|
|
client: one(users, {
|
|
fields: [sites.clientId],
|
|
references: [users.id],
|
|
}),
|
|
shifts: many(shifts),
|
|
}));
|
|
|
|
export const shiftsRelations = relations(shifts, ({ one, many }) => ({
|
|
site: one(sites, {
|
|
fields: [shifts.siteId],
|
|
references: [sites.id],
|
|
}),
|
|
assignments: many(shiftAssignments),
|
|
}));
|
|
|
|
export const shiftAssignmentsRelations = relations(shiftAssignments, ({ one }) => ({
|
|
shift: one(shifts, {
|
|
fields: [shiftAssignments.shiftId],
|
|
references: [shifts.id],
|
|
}),
|
|
guard: one(guards, {
|
|
fields: [shiftAssignments.guardId],
|
|
references: [guards.id],
|
|
}),
|
|
}));
|
|
|
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|
user: one(users, {
|
|
fields: [notifications.userId],
|
|
references: [users.id],
|
|
}),
|
|
}));
|
|
|
|
// ============= INSERT SCHEMAS =============
|
|
|
|
export const insertUserSchema = createInsertSchema(users).pick({
|
|
email: true,
|
|
firstName: true,
|
|
lastName: true,
|
|
profileImageUrl: true,
|
|
role: true,
|
|
});
|
|
|
|
export const insertGuardSchema = createInsertSchema(guards).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertCertificationSchema = createInsertSchema(certifications).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
status: true,
|
|
});
|
|
|
|
export const insertSiteSchema = createInsertSchema(sites).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertShiftSchema = createInsertSchema(shifts).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertShiftAssignmentSchema = createInsertSchema(shiftAssignments).omit({
|
|
id: true,
|
|
assignedAt: true,
|
|
});
|
|
|
|
export const insertNotificationSchema = createInsertSchema(notifications).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
// ============= TYPES =============
|
|
|
|
export type UpsertUser = typeof users.$inferInsert;
|
|
export type User = typeof users.$inferSelect;
|
|
|
|
export type InsertGuard = z.infer<typeof insertGuardSchema>;
|
|
export type Guard = typeof guards.$inferSelect;
|
|
|
|
export type InsertCertification = z.infer<typeof insertCertificationSchema>;
|
|
export type Certification = typeof certifications.$inferSelect;
|
|
|
|
export type InsertSite = z.infer<typeof insertSiteSchema>;
|
|
export type Site = typeof sites.$inferSelect;
|
|
|
|
export type InsertShift = z.infer<typeof insertShiftSchema>;
|
|
export type Shift = typeof shifts.$inferSelect;
|
|
|
|
export type InsertShiftAssignment = z.infer<typeof insertShiftAssignmentSchema>;
|
|
export type ShiftAssignment = typeof shiftAssignments.$inferSelect;
|
|
|
|
export type InsertNotification = z.infer<typeof insertNotificationSchema>;
|
|
export type Notification = typeof notifications.$inferSelect;
|
|
|
|
// ============= EXTENDED TYPES FOR FRONTEND =============
|
|
|
|
export type GuardWithCertifications = Guard & {
|
|
certifications: Certification[];
|
|
user?: User;
|
|
};
|
|
|
|
export type ShiftWithDetails = Shift & {
|
|
site: Site;
|
|
assignments: (ShiftAssignment & {
|
|
guard: GuardWithCertifications;
|
|
})[];
|
|
};
|
|
|
|
export type SiteWithShifts = Site & {
|
|
shifts: Shift[];
|
|
client?: User;
|
|
};
|