Coastal Timesheet v2 — full feature buildout

Features:
- Copy Previous Week (entries duplication)
- Bulk Approve/Reject (admin workflow)
- Overtime tracking (employee + admin views)
- DayCard component with auto-save
- PDF generation, email notifications
- React + Tailwind frontend, Prisma + PostgreSQL backend
- Docker deployment (3 containers)
This commit is contained in:
BizzleBot
2026-02-16 10:01:39 +00:00
commit f718a76153
65 changed files with 6756 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Role {
employee
admin
super_admin
}
enum TimesheetStatus {
draft
submitted
approved
rejected
}
model User {
id String @id @default(uuid())
email String @unique
name String
role Role @default(employee)
passwordHash String @map("password_hash")
isActive Boolean @default(true) @map("is_active")
refreshToken String? @map("refresh_token")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
timeEntries TimeEntry[]
timesheets Timesheet[] @relation("UserTimesheets")
approvals Timesheet[] @relation("ApprovedTimesheets")
@@map("users")
}
model Homeowner {
id String @id @default(uuid())
name String @unique
address String?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
timeEntries TimeEntry[]
@@map("homeowners")
}
model TimeEntry {
id String @id @default(uuid())
userId String @map("user_id")
date DateTime @db.Date
homeownerId String @map("homeowner_id")
hoursWorked Decimal @map("hours_worked") @db.Decimal(4, 2)
workDescription String @map("work_description")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
homeowner Homeowner @relation(fields: [homeownerId], references: [id])
timesheetLinks TimesheetEntry[]
@@index([userId, date])
@@index([homeownerId])
@@map("time_entries")
}
model Timesheet {
id String @id @default(uuid())
userId String @map("user_id")
weekStart DateTime @map("week_start") @db.Date
weekEnd DateTime @map("week_end") @db.Date
status TimesheetStatus @default(draft)
submittedAt DateTime? @map("submitted_at")
approvedBy String? @map("approved_by")
approvedAt DateTime? @map("approved_at")
notes String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation("UserTimesheets", fields: [userId], references: [id], onDelete: Cascade)
approver User? @relation("ApprovedTimesheets", fields: [approvedBy], references: [id])
entries TimesheetEntry[]
@@unique([userId, weekStart])
@@index([status])
@@index([userId, weekStart])
@@map("timesheets")
}
model TimesheetEntry {
id String @id @default(uuid())
timesheetId String @map("timesheet_id")
timeEntryId String @map("time_entry_id")
timesheet Timesheet @relation(fields: [timesheetId], references: [id], onDelete: Cascade)
timeEntry TimeEntry @relation(fields: [timeEntryId], references: [id], onDelete: Cascade)
@@unique([timesheetId, timeEntryId])
@@map("timesheet_entries")
}