A full-stack web app for assigning, tracking and approving work inside a company. A manager (the delegator) assigns a task to a colleague (the delegatee), and both follow it from creation to approval. Comments, file attachments, history and notifications are all kept in one place.
It was built for JK Botanicals to replace task tracking that had been spread across WhatsApp messages, calls and spreadsheets. With the app, everyone can see who owns each task, when it's due, and whether it has been done.
- Task delegation: create a task with a title, description, priority (Low / Medium / High / Urgent), due date and company, and assign it to any employee.
- Review-based workflow: the delegatee moves a task through Pending → In Progress → In Review, and only the delegator can approve it (Completed) or cancel it (Cancelled).
- Observers: add colleagues who can follow and comment on a task without being able to change it.
- Recurring tasks: tasks can repeat daily, weekly, monthly or yearly (e.g. "every 2 weeks"), either indefinitely or for a fixed number of occurrences. The schedule is based on each task's deadline.
- Comments, attachments and a full change history on every task.
- Notifications: in-app, by email, and on WhatsApp for key events (task assigned, task approved).
- Dashboard showing your open, overdue and completed tasks. Overdue tasks are marked in every task list.
- Admin tools: role management, employee offboarding with task reassignment, phone number management, and weekly or monthly performance scorecards for each employee.
- Bulk employee import from an Excel sheet.
- Role-based access control (RBAC): users have roles, and roles grant permissions. Every protected API route checks for a specific permission.
- Open the app and click Log In.
- Enter your Employee Code and the password you were given.
- Your session lasts 8 hours. After that you'll be asked to log in again.
The dashboard shows three counts for tasks assigned to you:
- Open: tasks you still need to finish
- Overdue: open tasks past their due date
- Completed: tasks your delegator has approved
- Click Create Task in the sidebar.
- Fill in the title, description, priority, due date and company, and choose the assignee.
- (Optional) Add observers by typing their names.
- (Optional) Turn on Repeats for a recurring task. Choose how often it repeats and, if you like, how many times. The next task is created automatically after each deadline.
- Click Create. The assignee is notified straight away.
- Open My Tasks and click View Task.
- Change the status to In Progress when you start.
- Use comments to ask questions or post updates, and attachments to upload files.
- When you've finished, change the status to In Review. This sends the task to your delegator for approval.
You can't mark your own task as Completed. Only the person who assigned it can approve it.
- Open Assigned to see everything you've delegated.
- When a task reaches In Review, open it and check the work.
- Set it to Completed to approve it, or Cancelled if it's no longer needed. To ask for changes, leave a comment.
- For recurring tasks, you can click Stop repeating at any time.
Observing lists the tasks you've been added to as an observer. You can read them and comment, but you can't change them.
The Notifications page (top bar, with an unread count) shows assignments, status changes and comments on your tasks. Mark notifications as read one at a time or all at once.
- Admin page: assign roles, edit employees' WhatsApp numbers, and offboard employees who leave, reassigning their open tasks to someone else.
- Scorecards: each employee's completion score for a selected week or month. Score = completed ÷ (completed + not done). "Not done" means cancelled or overdue. Tasks that aren't due yet don't count.
| Layer | Technologies |
|---|---|
| Frontend | React 19, TypeScript, Vite, React Router, Bootstrap 5 |
| Backend | Node.js, Express 5, TypeScript (run with tsx) |
| Database | PostgreSQL, Prisma ORM 7 (with the @prisma/adapter-pg driver adapter) |
| Authentication | Session-based auth with HttpOnly cookies, argon2 password hashing |
| File uploads | Multer |
| Data import | SheetJS (xlsx) for Excel employee imports |
| Notifications | Resend (email over an HTTP API), WhatsApp Business Cloud API (Meta) |
| Scheduling | cron-job.org calling a secured internal endpoint for recurring tasks |
| Hosting | Render (web service + PostgreSQL) |
| Tooling | ESLint, Prisma Migrate, Prisma Studio, Git and GitHub |
task-delegation-system/
├── backend/
│ ├── server.ts # Express entry point; also serves the built frontend
│ ├── routes/ # auth, tasks, users, roles, notifications, scorecards, internal (cron)
│ ├── middleware/ # requireAuth / requirePermission
│ ├── utils/ # password, sessions, RBAC seed, recurrence, email, WhatsApp, pagination
│ ├── imports/ # Excel / seed scripts (employees, departments, RBAC)
│ └── prisma/ # schema.prisma + migrations
├── frontend/
│ └── src/
│ ├── pages/ # Dashboard, task lists, task detail, admin, scorecards, ...
│ ├── components/ # AppShell, TaskListView, Pagination
│ └── context/ # auth context
└── README.md
Prerequisites: Node.js 20+ and PostgreSQL.
# 1. Backend
cd backend
npm install
# create backend/.env with:
# DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/task_delegation_db?schema=public"
# CRON_SECRET="<any random string>"
npx prisma migrate deploy # create the tables
npx prisma generate # generate the Prisma client
npx tsx imports/employees.ts # import employees from backend/employees.xlsx
npx tsx imports/rbac.ts --admin=<employeeCode> # make one user an admin
npm run dev # API on http://localhost:5000
# 2. Frontend (in a second terminal)
cd frontend
npm install
npm run dev # app on http://localhost:5173 (/api is proxied to :5000)Email (RESEND_API_KEY, SMTP_FROM) and WhatsApp (WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID) are optional. If they aren't set, those channels are skipped and in-app notifications still work.
For production, the backend serves the built frontend (frontend/dist) from a single origin, so the whole app deploys as one web service (it was hosted on Render).
1. Emails failing silently on Render (ENETUNREACH)
Gmail's SMTP host resolved to an IPv6 address, but Render has no outbound IPv6 route. Forcing IPv4 in nodemailer fixed that, until I found that Render blocks outbound SMTP ports (25/465/587) altogether.
Fix: I switched to the Resend HTTP API, which sends over port 443. I also made email optional, so the app keeps working when it isn't configured.
2. No background workers or cron on the free tier
Recurring tasks need something to create the next occurrence on schedule, but Render's free tier has no cron jobs or background workers.
Fix: I added an internal endpoint, POST /api/internal/run-cyclical-tasks, protected by a shared-secret header, and called it from cron-job.org. The endpoint is idempotent and catches up on missed runs, so duplicate or skipped calls do no harm.
3. Off-by-one dates in recurring tasks The first design based each occurrence on its time period ("every month starting X"). As a result, a task's due date sometimes didn't match the date the user had entered, and month-end dates such as Jan 31 rolled over into the next month. Fix: I redesigned it around the deadline: next deadline = deadline + interval. Monthly and yearly steps are clamped to the end of the month, so Jan 31 → Feb 28/29. I also added an optional limit on the number of repeats.
4. Build failures on Render
Render sets NODE_ENV=production, so npm install skipped devDependencies (vite, typescript, tsx, prisma) and the build failed.
Fix: I added --include=dev to the build command. A separate cd: command not found error came from a zero-width character that had been copied in with a command from rendered Markdown. I fixed it by typing the command by hand.
5. No shell access for migrations
Render's free tier has no Shell tab, so I couldn't run prisma migrate deploy on the server.
Fix: I ran migrations and seed scripts from my own machine against the database's external URL, and documented this as a required step for every schema change.
6. Prisma 7 changes
Prisma 7 doesn't load .env automatically and needs a driver adapter to connect.
Fix: I configured the CLI through prisma.config.ts with dotenv, used @prisma/adapter-pg in the client singleton, and generated the client into a custom output folder.
7. Picking employees from a long list A multi-select dropdown listing every employee was slow to use for adding observers. Fix: I replaced it with a search-as-you-type picker, which shows selected people as removable tags.
8. Slow task lists as data grew Task and notification lists loaded everything at once. Fix: I added server-side pagination (20 per page, 50 max) through a shared helper, plus database indexes on the most-queried columns.
9. Real-world data that didn't fit the schema Some employees share an email address, which broke the unique constraint on email during the Excel import. Fix: I dropped the unique constraint on email and used employee code as the unique login identifier.
10. Free database expiry and short-lived files Render's free Postgres is deleted after 90 days, and uploaded files are lost on every redeploy. Fix: the app runs against a local PostgreSQL for demos, using the same migrations. Moving attachments to S3-compatible storage is the next planned step.
- Designing a relational schema with RBAC, self-referencing recurring records and cascading child data in Prisma
- Building session-based auth from scratch: secure cookies, server-side sessions, password hashing
- Working within free-tier hosting limits (no cron, no SMTP, ephemeral disk, expiring databases)
- Making external integrations (email, WhatsApp) fail safely so the core app always works
- Handling dates correctly: month-end clamping, a single DD/MM/YYYY display format, and calculating overdue status
- Move attachments to object storage (S3 / Cloudflare R2)
- Automated tests for the API and the recurrence logic
- Password reset / change-password flow
- Better mobile layout