A lightweight, role‑based employee management system designed for organizations with a Global Admin and fixed Province Admins.
The system provides a clean separation of access, scoped data visibility, and a simple front‑end flow.
Below are some screenshots of the IRC Employee Management System in action:
The project is organized as a pnpm workspace with two sub‑projects:
.
├── client/ # React + Vite front‑end
│ ├── public/
│ ├── src/
│ ├── package.json
│ └── …
├── server/ # Express + TypeORM back‑end
│ ├── src/
│ ├── jest.config.js
│ └── …
├── PERFORMANCE_LOCK_*.md # design notes for the performance‑lock feature
├── routing.md # api routing notes
└── README.md # you are here
Backend
- Node.js + Express + TypeScript
- MongoDB with TypeORM
- Session-based authentication
- Jest for unit/integration tests
Frontend
- React 18 + TypeScript + Vite
- Material-UI (MUI) v5
- React Router + Axios
- ESLint/Prettier configured via
eslint.config.js
- Node.js 18+ (or LTS)
- pnpm (see https://pnpm.io/)
- MongoDB instance (local or remote)
Clone the repository and install dependencies from the root:
git clone <repo-url>
cd IRC-StaffSystem
pnpm installThis will install packages for both client and server.
Create a .env file in server/ with at least:
PORT=4000
MONGO_URI=mongodb://localhost:27017/irc-staff
SESSION_SECRET=super-secretThe client does not require environment variables for basic development; see client/vite.config.ts for the few that exist (VITE_API_BASE).
cd server
pnpm dev # ts-node‑dev / nodemon – watches files
# or to build & start
pnpm build
pnpm startThe server listens on http://localhost:4000 by default.
cd client
pnpm devOpen http://localhost:5173 (or the port printed by Vite).
From the repo root you can run the two concurrently with tools such as concurrently, or use two terminals:
pnpm --filter server dev
pnpm --filter client devThe client is configured to proxy API requests to the server (/api/*), see vite.config.ts.
The system manages employees across multiple provinces.
There are only two roles:
- Global Admin — can view all provinces and browse employees within any province.
- Province Admin — can only manage employees within their own province.
Province admins themselves are fixed (not created through the UI or API).
- Secure login (
/auth/login) - Global Admin dashboard showing all provinces
- Province‑scoped employee management
- Create, edit, delete employees (province‑scoped)
- Performance record management (add, edit, delete)
- Global Performance Lock — Prevent all employees from editing performance records system-wide
- Fetch employees belonging to a selected province
- Material-UI frontend with custom theme
- No generic
/employeesroot — everything is province‑scoped
All employee data is nested under provinces.
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/login |
Login as Global Admin or Province Admin |
| Method | Endpoint | Description |
|---|---|---|
| GET | /provinces |
List all provinces (Global Admin only) |
| GET | /provinces/:provinceId |
Get details of a specific province |
| Method | Endpoint | Description |
|---|---|---|
| GET | /global-settings |
Get global settings (performance lock status) |
| POST | /global-settings/toggle-performance-lock |
Toggle performance editing lock (Global Admin only) |
All employee operations must include the province they belong to.
| Method | Endpoint | Description |
|---|---|---|
| GET | /provinces/:provinceId/employees |
List all employees of the province |
| POST | /provinces/:provinceId/employees |
Create a new employee in the province |
| GET | /provinces/:provinceId/employees/:employeeId |
Fetch a single employee |
| PUT | /provinces/:provinceId/employees/:employeeId |
Update an employee |
| DELETE | /provinces/:provinceId/employees/:employeeId |
Delete an employee |
- User enters credentials
- Sends POST →
/auth/login - Redirects based on role
GlobalAdminDashboardPage
Displays a list of all provinces (GET /provinces).
User selects a province → redirect to:
/provinces/:provinceId/employees
Which loads the employee list for that province.
ProvinceEmployeesPage
Displays all employees of their province:
GET /provinces/:provinceId/employees
Actions:
- "Create Employee" →
NewEmployeeFormPage - Select employee →
EmployeePage(/provinces/:provinceId/employees/:employeeId) - Edit employee →
EditEmployeeDialog(modal) - Manage performance →
PerformanceManagercomponent
The Global Admin can lock/unlock performance editing across the entire system.
-
Locking: Global Admin clicks the lock toggle on the dashboard
- Sets
performanceLocked: truein global settings - All employees receive HTTP 423 (Locked) when attempting to edit performance
- Sets
-
UI Feedback:
- Lock toggle button shows current state (🔒 locked / 🔓 unlocked)
- Toast notification displays with distinct messages and colors:
- Warning (orange): "Performance editing is now LOCKED"
- Success (green): "Performance editing is now UNLOCKED"
- Employees see alert when locked: "Performance records are currently locked. You cannot make changes at this time."
-
Reset All button is disabled when lock is active (prevents accidental resets during lock period)
Global Admin can reset all employee performance metrics to defaults:
- Preserved: Employee status (remains unchanged)
- Reset to defaults:
- Daily performance: 0
- Shift count per location: 0
- Shift duration: 8 hours
- Overtime: 0
- Daily leave: 0
- Sick leave: 0
- Absence: 0
- Travel assignment: 0
- Notes: cleared
{
"success": true,
"data": {
"_id": "...",
"performanceLocked": true,
"lastLockedBy": "admin_user_id",
"lockedAt": "2025-12-28T...",
"createdAt": "...",
"updatedAt": "..."
}
}{
id: string,
email: string,
password: string (hashed),
role: "global" | "province",
provinceId?: string
}
{
id: string,
name: string
}
{
id: string,
firstName: string,
lastName: string,
phone: string,
nationalId: string,
provinceId: string
}
Employees always reference the province they belong to.
{
id: string,
performanceLocked: boolean,
lastLockedBy: string (user id),
lockedAt: Date,
createdAt: Date,
updatedAt: Date
}
Stores system-wide settings including the performance lock status.
- Minimalistic & strict: no unnecessary endpoints
- 100% province‑scoped employees
- Global admin ≠ province admin list viewer
- Predictable URL structure
- Easy to port into any client framework
This system provides:
- Clean role‑based structure
- Simple routes
- Hierarchical API (
/provinces → employees) - No redundant admin management
- Production‑ready separation of access
Perfect for organizational employee management with fixed province administration.
-
Server tests live in
server/src/__tests__; run with:cd server pnpm test
-
Client currently has no automated tests – end‑to‑end tests may be added in the future.
-
Linting/formatting (frontend) is enforced by running:
cd client pnpm lint pnpm format
The server project uses TypeScript's strict mode and eslint is enabled via npm run lint inside server.
- Routing is defined in
server/src/routes; seerouting.mdfor the decision‑making process. - Performance lock logic lives in
server/src/controllers/globalSettingsController.tsand is documented inPERFORMANCE_LOCK_FEATURE.md. - Front‑end pages and components reside under
client/src/pagesandclient/src/components; look forGlobalAdminDashboardPage,ProvinceEmployeesPage, etc.
- Build the server:
cd server && pnpm build. - Build the client:
cd client && pnpm build. - Serve the
client/distfolder with a static server, or configure the Express app to serve it (not implemented out‑of‑the-box).
- routing.md – rationale for API structure.
- PERFORMANCE_LOCK_FEATURE.md – deep dive on the lock feature.
client/IMPLEMENTATION.md– frontend implementation notes.server/TEST_SETUP.md– instructions for test database seeding.
This project is licensed under the MIT License.
This README has been updated to reflect the current codebase structure and development workflow.








