Staff Roles
This guide covers role management in Shyfts, including role hierarchy, permissions, role assignment, and customisation options.
Understanding Roles
Role Purpose
Staff roles in Shyfts serve multiple purposes:
- Identification - Categorise staff by their function
- Permissions - Control access to system features
- Scheduling - Filter staff by role for scheduling
- Visual Identity - Colour-coded role indicators throughout the system
- Reporting - Generate role-based analytics
Role Database Structure
// Source: src/types/database.types.ts:2671-2716
staff_roles: {
Row: {
id: string
company_id: string
name: string
display_name: string | null
description: string | null
color: string | null
hierarchy_level: number | null
can_manage_schedules: boolean | null
can_manage_staff: boolean | null
can_view_reports: boolean | null
permissions: Json | null
created_at: string | null
updated_at: string | null
}
}
Role Hierarchy
Hierarchy Levels
Shyfts uses a numeric hierarchy system to determine role authority:
| Level | Role Type | Description |
|---|---|---|
| 1 | Manager | Full company management access |
| 2 | Senior Staff | Limited management capabilities |
| 3+ | Regular Staff | Standard staff permissions |
Hierarchy in Practice
// Source: src/app/api/companies/[companyId]/staff/route.ts:94-111
if (roleId) {
const { data: role, error: roleError } = await supabase
.from('staff_roles')
.select('id, hierarchy_level')
.eq('id', roleId)
.eq('company_id', params.companyId)
.single()
if (roleError || !role) {
return ApiResponses.badRequest('Role does not belong to this company')
}
// hierarchy_level = 1 -> COMPANY_MANAGER
// hierarchy_level >= 2 -> STAFF
hierarchyLevel = role.hierarchy_level ?? 2
}
System Role Assignment
| Hierarchy Level | System Role | Capabilities |
|---|---|---|
| 1 | COMPANY_MANAGER | Full dashboard access, staff management |
| 2+ | STAFF | Staff portal access only |
Role Properties
Core Properties
| Property | Type | Description |
|---|---|---|
| name | String | Internal reference name |
| display_name | String | User-facing label |
| description | Text | Role purpose/responsibilities |
| color | Hex | Visual identifier (e.g., #FFB5B0) |
| hierarchy_level | Number | Authority level (1 = highest) |
Permission Flags
| Permission | Type | Controls |
|---|---|---|
| can_manage_schedules | Boolean | Create/edit shifts |
| can_manage_staff | Boolean | Add/edit staff members |
| can_view_reports | Boolean | Access reporting features |
Extended Permissions
The permissions JSON field allows granular control:
{
"scheduling": {
"create": true,
"edit": true,
"delete": false
},
"staff": {
"view": true,
"create": false,
"edit": false
},
"reports": {
"view": true,
"export": false
},
"leave": {
"approve": false,
"request": true
}
}
Industry Templates
Default Roles by Industry
Roles are pre-configured based on your company's industry template:
GP Practice
| Role | Colour | Hierarchy |
|---|---|---|
| Practice Manager | #4F46E5 | 1 |
| Senior GP | #10B981 | 2 |
| GP | #22C55E | 3 |
| Nurse | #EF4444 | 3 |
| Receptionist | #F59E0B | 3 |
| Administrator | #6B7280 | 3 |
Dental Practice
| Role | Colour | Hierarchy |
|---|---|---|
| Practice Manager | #4F46E5 | 1 |
| Principal Dentist | #10B981 | 2 |
| Associate Dentist | #22C55E | 3 |
| Dental Hygienist | #3B82F6 | 3 |
| Dental Nurse | #EF4444 | 3 |
| Receptionist | #F59E0B | 3 |
Restaurant
| Role | Colour | Hierarchy |
|---|---|---|
| General Manager | #4F46E5 | 1 |
| Head Chef | #EF4444 | 2 |
| Sous Chef | #F97316 | 3 |
| Line Cook | #FBBF24 | 3 |
| Server | #22C55E | 3 |
| Host | #3B82F6 | 3 |
Office
| Role | Colour | Hierarchy |
|---|---|---|
| Office Manager | #4F46E5 | 1 |
| Team Lead | #10B981 | 2 |
| Senior Staff | #22C55E | 3 |
| Staff | #6B7280 | 3 |
| Administrator | #F59E0B | 3 |
Assigning Roles
During Staff Creation
When adding a new staff member, select their role from the dropdown:
// Source: src/components/staff/EnhancedStaffForm.tsx:573-596
<div className="form-field">
<label className="form-label required">Role</label>
<select
{...register('role_id')}
className="form-select"
disabled={loadingRoles}
>
<option value="" disabled>
{loadingRoles ? 'Loading roles...' : 'Select a role...'}
</option>
{roles.map((role) => (
<option key={role.id} value={role.id}>
{role.display_name || role.name}
</option>
))}
</select>
{errors.role_id && (
<p className="text-sm text-red-400 mt-1">{errors.role_id.message}</p>
)}
</div>
Role Loading
Roles are loaded for the specific company:
// Source: src/components/staff/EnhancedStaffForm.tsx:140-152
async function loadRoles() {
if (!companyId) return
try {
setLoadingRoles(true)
const response = await fetch(`/api/companies/${companyId}/roles`)
const data = await response.json()
setRoles(data.roles || [])
} catch (error) {
console.error('Error loading roles:', error)
} finally {
setLoadingRoles(false)
}
}
Changing Staff Role
To change a staff member's role:
- Navigate to Staff Management
- Click on the staff member
- Click Edit Profile
- Select new role from the dropdown
- Click Save Changes
Changing a staff member's role may affect:
- Their system access permissions
- Their visibility in scheduling filters
- Their reporting categorisation
- Calendar colour display
Role Colours
Purpose
Role colours provide visual identification throughout Shyfts:
- Staff List - Role badge colouring
- Calendar - Shift block colours
- Reports - Chart segment colours
- Staff Cards - Role indicator dots
Colour Display
// Source: src/components/staff/StaffList.tsx:196-200
<span
className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100"
style={{ color: member.statusColor }}>
{member.statusDisplay}
</span>
Shift Block Colouring
// Source: src/components/calendar/RoomTimeGridCalendar.tsx:312-315
style={{
left: `${left}px`,
width: `${width}px`,
backgroundColor: shift.staff?.role?.color || '#FFB5B0'
}}
Default Fallback
If no colour is set, shifts display with the coral accent (#FFB5B0).
Security Considerations
Cross-Company Protection
Roles are scoped to individual companies. The system validates that:
- Role belongs to company - Prevents cross-company role assignment
- Hierarchy is respected - Staff cannot assign roles above their level
- Permissions are enforced - Actions checked against role permissions
// Source: src/app/api/companies/[companyId]/staff/route.ts:97-106
const { data: role, error: roleError } = await supabase
.from('staff_roles')
.select('id, hierarchy_level')
.eq('id', roleId)
.eq('company_id', params.companyId)
.single()
if (roleError || !role) {
return ApiResponses.badRequest('Role does not belong to this company')
}
Permission Enforcement
Role-based access is enforced at multiple levels:
| Level | Protection |
|---|---|
| Database | Row Level Security (RLS) policies |
| API | Route-level role checks |
| Frontend | Conditional UI rendering |
| Context | User role state management |
API Endpoints
List Company Roles
Endpoint: GET /api/companies/[companyId]/roles
Response:
{
"success": true,
"roles": [
{
"id": "uuid",
"name": "nurse",
"display_name": "Nurse",
"description": "Clinical nursing staff",
"color": "#EF4444",
"hierarchy_level": 3,
"can_manage_schedules": false,
"can_manage_staff": false,
"can_view_reports": true
}
]
}
Create Role
Endpoint: POST /api/companies/[companyId]/roles
Request Body:
{
"name": "senior_nurse",
"display_name": "Senior Nurse",
"description": "Senior nursing staff with additional responsibilities",
"color": "#DC2626",
"hierarchy_level": 2,
"can_manage_schedules": true,
"can_manage_staff": false,
"can_view_reports": true
}
Update Role
Endpoint: PUT /api/companies/[companyId]/roles/[roleId]
Request Body:
{
"display_name": "Updated Name",
"color": "#3B82F6",
"can_manage_schedules": true
}
Delete Role
Endpoint: DELETE /api/companies/[companyId]/roles/[roleId]
A role cannot be deleted if staff members are assigned to it. Reassign staff first.
Best Practices
Role Design
- Keep it simple - Don't create too many roles
- Clear naming - Use descriptive display names
- Distinct colours - Choose contrasting colours for visual clarity
- Appropriate hierarchy - Set hierarchy levels that reflect organisational structure
Permission Assignment
- Principle of least privilege - Only grant necessary permissions
- Separate duties - Different roles for different responsibilities
- Regular review - Periodically audit role permissions
- Document changes - Track permission modifications
Hierarchy Guidelines
| Level | Typical Roles |
|---|---|
| 1 | Managers, Directors, Practice Owners |
| 2 | Team Leaders, Senior Staff, Supervisors |
| 3 | Regular Staff, Associates, Assistants |
| 4 | Trainees, Temporary Staff, Contractors |
Troubleshooting
Role Not Appearing
| Issue | Solution |
|---|---|
| Role not in dropdown | Verify role belongs to correct company |
| Role created but missing | Refresh the page or re-fetch roles |
| Deleted role still showing | Clear browser cache |
Permission Issues
| Issue | Solution |
|---|---|
| Staff can't access feature | Check role permission flags |
| Manager missing permissions | Verify hierarchy_level is 1 |
| Inconsistent access | Check for conflicting RLS policies |
Related Documentation
- Adding Staff - Create new staff
- Editing Staff - Update staff profiles
- Deactivating Staff - Staff offboarding
Source Files:
src/types/database.types.ts- Role type definitionssrc/components/staff/EnhancedStaffForm.tsx- Role selection UIsrc/app/api/companies/[companyId]/staff/route.ts- Role validationsrc/app/api/companies/[companyId]/roles/route.ts- Role management API