v2
On mobile, typing the first characters of a work description on a new entry caused the form to remount: the auto-save POST replaced the temp entry with the server copy, changing its React key, which destroyed the textarea, dropped focus and closed the keyboard. - Key EntryForm on a stable clientKey that survives the id swap - Merge only the server id into the local entry so keystrokes typed while the create request is in flight are kept - Track temp-id -> real-id and in-flight creates so queued changes become PUTs instead of duplicate POSTs; deletes resolve to the real row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Coastal Timesheet v2
A modern, mobile-first time tracking application for Coastal Contracting of FL
✨ Features
- 📱 Mobile-first design — Built for field workers, optimized for phones
- 🔐 JWT authentication — Secure login with access/refresh tokens + rate limiting
- 📅 Weekly timesheets — Monday–Sunday pay period with auto-save
- 🏠 Multiple homeowners per day — Track work at different job sites
- 📋 Copy Previous Week — One tap to duplicate last week's entries as a template
- ✅ Submit → Approve workflow — Employees submit, admins approve or reject
- ⚡ Bulk Approve/Reject — Select all + approve 40 timesheets in one click
- ⏱️ Overtime Tracking — Real-time OT warnings for employees, admin reports with per-employee weekly breakdown
- 📄 PDF generation — Professional server-side PDF export
- 📧 Email integration — Send timesheets via email with SMTP
- 👥 Admin panel — Manage employees, homeowners, review timesheets
- 📊 Reporting — Filter by employee, homeowner, date range, status
- 🏡 Homeowner management — Add, edit, search, activate/deactivate with address fields
- 🌙 Dark mode — System-aware with manual toggle
- 🐳 One-command deploy — Single
docker compose upfor the entire stack
📸 Screenshots
| Timesheet Entry | Copy Previous Week | Dark Mode |
![]() |
![]() |
![]() |
| Bulk Approve | Overtime Tracking | Reports & Filters |
![]() |
![]() |
![]() |
| Homeowner Management | History | Entry Form |
![]() |
![]() |
![]() |
Desktop
🚀 Quick Start
Prerequisites
- Docker and Docker Compose
- That's it. Everything else runs in containers.
Deploy
# Clone the repo
git clone https://git.bizzle.lol/bizzle/coastal_timesheet.git
cd coastal_timesheet
git checkout v2
# Configure environment
cp .env.example .env
# Edit .env with your secrets (see Configuration below)
# Launch
cd docker
docker compose up -d
The app will be available at http://localhost (port 80).
Default Admin Account
| Field | Value |
|---|---|
admin@coastal.com |
|
| Password | CoastalAdmin2026! |
⚠️ Change the admin password after first login.
⚙️ Configuration
Copy .env.example to .env and configure:
# Database (auto-configured in Docker)
DB_PASSWORD=your-secure-db-password
# JWT Secrets (CHANGE THESE!)
JWT_SECRET=your-jwt-secret-min-32-chars
JWT_REFRESH_SECRET=your-refresh-secret-min-32-chars
# CORS (add your domain)
CORS_ORIGINS=https://timesheets.yourdomain.com
# Email (optional — for sending timesheets)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
SMTP_FROM=timesheets@yourdomain.com
ADMIN_EMAIL=admin@yourdomain.com
Email Setup (Gmail)
- Enable 2FA on your Google account
- Go to App Passwords
- Generate a new app password for "Mail"
- Use that as
SMTP_PASS
🏗️ Architecture
┌─────────────────────────────────────────────┐
│ Nginx │
│ (reverse proxy) │
│ /api/* → backend:3001 │
│ /* → static frontend │
├──────────────────┬──────────────────────────┤
│ Frontend │ Backend │
│ React + Vite │ Express + Prisma │
│ Tailwind CSS │ JWT Auth │
│ Lucide Icons │ @react-pdf/renderer │
│ │ Nodemailer │
│ ├──────────────────────────┤
│ │ PostgreSQL 16 │
│ │ (persistent volume) │
└──────────────────┴──────────────────────────┘
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite 6, Tailwind CSS 3, Lucide |
| Backend | Express 4, Prisma ORM, bcryptjs, jsonwebtoken |
| Database | PostgreSQL 16 (Alpine) |
| @react-pdf/renderer (server-side) | |
| Nodemailer + SMTP | |
| Proxy | Nginx 1.27 (Alpine) |
| Container | Docker Compose v3.9 |
📁 Project Structure
.
├── frontend/ # React SPA
│ ├── src/
│ │ ├── api/ # Axios client with token refresh
│ │ ├── components/ # Reusable UI components
│ │ ├── contexts/ # Auth context (JWT)
│ │ ├── hooks/ # Custom hooks (theme, swipe, auto-save)
│ │ └── pages/ # Route pages
│ └── vite.config.js
├── backend/ # Express API
│ ├── prisma/
│ │ ├── schema.prisma # Database schema
│ │ └── seed.js # Seed admin + homeowners
│ └── src/
│ ├── middleware/ # Auth middleware
│ ├── routes/ # API routes
│ └── utils/ # PDF, email, validation
├── docker/ # Deployment
│ ├── docker-compose.yml
│ ├── backend/Dockerfile
│ ├── frontend/Dockerfile
│ └── nginx/default.conf
└── docs/screenshots/ # App screenshots
🔒 Security
- bcrypt password hashing (12 rounds)
- JWT access tokens (15min) + refresh tokens (7 days)
- Helmet security headers
- Rate limiting on auth endpoints (50 req / 15 min)
- Zod input validation on all endpoints
- Prisma ORM — parameterized queries (no SQL injection)
- Non-root Docker containers
- CORS origin whitelist
📡 API Endpoints
Auth
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login |
Login, get tokens |
| POST | /api/auth/refresh |
Refresh access token |
| GET | /api/auth/me |
Current user info |
Entries
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/entries |
List entries (by week) |
| POST | /api/entries |
Create entry |
| PUT | /api/entries/:id |
Update entry |
| DELETE | /api/entries/:id |
Delete entry |
Timesheets
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/timesheets |
Get current week |
| GET | /api/timesheets/history |
All user timesheets |
| POST | /api/timesheets/submit |
Submit for approval |
| GET | /api/timesheets/:id/pdf |
Download PDF |
Admin
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/timesheets |
All timesheets (filter) |
| GET | /api/admin/timesheets/:id |
Timesheet detail |
| PUT | /api/admin/timesheets/:id/approve |
Approve timesheet |
| PUT | /api/admin/timesheets/:id/reject |
Reject timesheet |
| PUT | /api/admin/timesheets/:id/reopen |
Reopen for editing |
| GET | /api/admin/users |
List employees |
| POST | /api/admin/users |
Create employee |
| GET | /api/admin/homeowners |
List homeowners |
| POST | /api/admin/homeowners |
Add homeowner |
| GET | /api/admin/reports |
Reporting with filters |
🔄 Upgrading from v1
v2 is a complete rewrite. Key differences:
| Feature | v1 | v2 |
|---|---|---|
| Storage | Browser localStorage | PostgreSQL database |
| Auth | None | JWT with roles |
| Multi-user | No | Yes — unlimited employees |
| Approval flow | No | Submit → Approve/Reject |
| Client-side (jsPDF) | Server-side (@react-pdf) | |
| mailto: link | SMTP with PDF attachment | |
| Deploy | Static HTML | Docker Compose (one command) |
| Admin panel | No | Full admin with reporting |
| Dark mode | No | System-aware + manual toggle |
📝 License
Private — Coastal Contracting of FL. All rights reserved.
Built with ☀️ in Florida
Description
Modern, mobile-first time tracking app for Coastal Contracting of FL — React + Express + PostgreSQL + Docker
Readme
6.5 MiB
Languages
JavaScript
95.1%
CSS
2.8%
Dockerfile
1.8%
HTML
0.3%









