Leave Types
This guide covers leave type configuration, management, and how leave types integrate with staff leave balances and requests throughout the Shyfts system.
Overview
What Are Leave Types?
Leave types define the categories of time off available to staff members in your organisation. Each leave type has its own:
- Annual Allocation - Days entitled per year
- Colour Coding - Visual identification throughout the system
- Carry-Over Rules - Whether unused days roll over to next year
- Approval Requirements - Whether manager approval is needed
Database Structure
// Source: src/types/database.types.ts:925-937
leave_types: {
Row: {
id: string
company_id: string
name: string
description: string | null
annual_allocation_days: number
color: string | null
allow_carry_over: boolean | null
max_carry_over_days: number | null
requires_approval: boolean | null
created_at: string | null
updated_at: string | null
}
}
Default Leave Types
Standard Leave Types
When a company is created, Shyfts sets up common leave types:
| Leave Type | Colour | Default Days | Description |
|---|---|---|---|
| Annual Leave | #3B82F6 (Blue) | 25-28 | Yearly vacation entitlement |
| Sick Leave | #EF4444 (Red) | 10 | Medical-related absence |
| Personal Leave | #8B5CF6 (Purple) | 3-5 | Personal time off |
// Source: src/components/staff/StaffLeaveRequests.tsx:62-66
setLeaveTypes([
{ id: '1', name: 'Annual Leave', color: '#3B82F6', description: 'Yearly vacation time' },
{ id: '2', name: 'Sick Leave', color: '#EF4444', description: 'Medical related absence' },
{ id: '3', name: 'Personal Leave', color: '#8B5CF6', description: 'Personal time off' }
])
Industry-Specific Leave Labels
Leave types may use industry-specific terminology:
// Source: src/utils/api/industryTemplates.ts:291-295
{
leave: 'Annual Leave', // Healthcare: "Annual Leave"
overtime: 'Extra Sessions'
}
Leave Type Properties
Core Properties
| Property | Type | Required | Description |
|---|---|---|---|
| name | String | ✅ Yes | Display name of the leave type |
| description | String | ❌ No | Additional details about the leave type |
| annual_allocation_days | Number | ✅ Yes | Days allocated per year (default: varies) |
| color | Hex String | ❌ No | Visual colour for calendar/badges |
| requires_approval | Boolean | ❌ No | Whether manager approval is required |
Carry-Over Settings
| Property | Type | Default | Description |
|---|---|---|---|
| allow_carry_over | Boolean | false | Whether unused days can be carried to next year |
| max_carry_over_days | Number | null | Maximum days that can be carried over (if allowed) |
Example Leave Type Record
{
"id": "type-annual",
"company_id": "company-456",
"name": "Annual Leave",
"description": "Standard annual holiday entitlement",
"annual_allocation_days": 28,
"color": "#10B981",
"allow_carry_over": true,
"max_carry_over_days": 5,
"requires_approval": true,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
Leave Type Colours
Standard Colour Palette
| Leave Type | Hex Code | Visual |
|---|---|---|
| Annual Leave | #3B82F6 | Blue |
| Annual Leave (Alt) | #10B981 | Green |
| Sick Leave | #EF4444 | Red |
| Personal Leave | #8B5CF6 | Purple |
| Compassionate | #EC4899 | Pink |
| Maternity/Paternity | #F59E0B | Amber |
| Study Leave | #06B6D4 | Cyan |
| Unpaid Leave | #6B7280 | Grey |
Colour Display in UI
Colours appear throughout the system:
// Source: src/lib/adapters/__tests__/leave-request.adapter.snapshot.test.ts:42
{
name: 'Annual Leave',
color: '#10B981',
annual_allocation_days: 28
}
- Calendar View - Leave blocks use type colour
- Request List - Type badges show colour
- Balance Cards - Progress bars use type colour
- Reports - Charts segment by type colour
Fetching Leave Types
API Endpoint
Endpoint: GET /api/staff/leave-types
// Source: src/app/api/staff/leave-types/route.ts
export async function GET() {
const { data: leaveTypes } = await supabase
.from('leave_types')
.select('*')
.eq('company_id', companyId)
.order('name', { ascending: true })
return NextResponse.json(leaveTypes)
}
Response:
[
{
"id": "uuid-1",
"name": "Annual Leave",
"description": "Yearly vacation entitlement",
"annual_allocation_days": 28,
"color": "#3B82F6",
"allow_carry_over": true,
"max_carry_over_days": 5,
"requires_approval": true
},
{
"id": "uuid-2",
"name": "Sick Leave",
"description": "Medical-related absence",
"annual_allocation_days": 10,
"color": "#EF4444",
"allow_carry_over": false,
"max_carry_over_days": null,
"requires_approval": false
}
]
Database Query
// Source: src/lib/db/leave-request.db.ts:685-700
export async function getLeaveTypes(companyId: string) {
const { result, timing } = await trackPerformance('getLeaveTypes', async () => {
const supabase = await createClient()
const { data, error } = await supabase
.from('leave_types')
.select('*')
.eq('company_id', companyId)
.order('name', { ascending: true })
if (error) handleDBError(error)
return data || []
})
return { data: result, timing }
}
Leave Type Validation
Common Leave Type Categories
// Source: src/lib/validation/leave-request.schemas.ts:23-37
// Leave type enum (common types, can be extended)
const leaveTypeSchema = z.enum([
'annual',
'sick',
'maternity',
'paternity',
'compassionate',
'unpaid',
'study',
'jury_duty',
'public_duty',
'other'
])
Leave Request Type Reference
When creating leave requests, the leave_type_id references the leave types table:
// Source: src/lib/validation/leave-request.schemas.ts:46-48
baseLeaveRequestSchema = z.object({
leave_type_id: uuidSchema.describe('Leave type ID from company settings'),
// ... other fields
})
Leave Balance Integration
Balance Tracking Per Type
Each staff member has a balance tracked for each leave type:
// Source: src/hooks/useLeaveBalance.ts
interface LeaveBalance {
leave_type_id: string
leave_type_name: string
entitlement_days: number // Total annual allocation
used_days: number // Days already taken
pending_days: number // Days awaiting approval
available_days: number // Days remaining to use
carry_over_days?: number // Days carried from last year
expires_date?: string // When carried days expire
color?: string // Leave type colour
}
Balance Calculation
| Field | Calculation |
|---|---|
| entitlement_days | annual_allocation_days + carry_over_days |
| used_days | Sum of approved leave requests in current year |
| pending_days | Sum of pending leave requests |
| available_days | entitlement_days - used_days - pending_days |
Balance Schema Validation
// Source: src/lib/validation/leave-request.schemas.ts:161-182
leaveBalanceSchema = z.object({
staff_id: uuidSchema,
leave_type_id: uuidSchema,
year: z.number().int().min(2000).max(2100),
entitlement: z.number().min(0).max(365),
used: z.number().min(0),
pending: z.number().min(0),
carried_forward: z.number().min(0).optional().default(0)
}).refine(
data => data.used <= data.entitlement + data.carried_forward,
{ message: 'Used days cannot exceed total available days' }
)
Leave Type Selection
Staff Leave Request Form
When staff create leave requests, they select from available leave types:
// Source: src/components/staff/StaffLeaveRequests.tsx:291-300
<label className="block text-sm font-medium text-primary-text mb-1">
Leave Type *
</label>
<select
value={formData.leave_type_id}
onChange={(e) => setFormData(prev => ({
...prev,
leave_type_id: e.target.value
}))}
className="form-input w-full"
required
>
<option value="">Select leave type</option>
{leaveTypes.map(type => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
Form Data Structure
// Source: src/components/staff/StaffLeaveRequests.tsx:25-29
const [formData, setFormData] = useState({
leave_type_id: '',
start_date: '',
end_date: '',
reason: ''
})
Validation on Submit
// Source: src/components/staff/StaffLeaveRequests.tsx:80-83
if (!formData.leave_type_id || !formData.start_date || !formData.end_date) {
setError('Please fill in all required fields')
return
}
Carry-Over Rules
How Carry-Over Works
| Setting | Behaviour |
|---|---|
allow_carry_over = false | Unused days expire at year end |
allow_carry_over = true | Unused days roll to next year |
max_carry_over_days = 5 | Maximum 5 days can be carried |
max_carry_over_days = null | Unlimited carry-over (if allowed) |
Carry-Over Example
Scenario: Staff member has 28 days annual leave, uses 25 days
| Setting | Result |
|---|---|
| No carry-over | 3 days lost at year end |
| Carry-over, max 5 | 3 days carry to next year |
| Carry-over, unlimited | All 3 days carry to next year |
Expiry Tracking
Carried-over days may have an expiry date:
// Source: src/hooks/useLeaveBalance.ts
{
carry_over_days: 3,
expires_date: '2025-03-31' // Carry-over expires Q1
}
Requires Approval Setting
Approval Behaviour
| Setting | Behaviour |
|---|---|
requires_approval = true | Request goes to manager for approval |
requires_approval = false | Request auto-approved (or no approval needed) |
Common Configurations
| Leave Type | Typically Requires Approval |
|---|---|
| Annual Leave | ✅ Yes - Planning required |
| Sick Leave | ❌ No - Often self-certified |
| Compassionate | ✅ Yes - Verification may be needed |
| Unpaid Leave | ✅ Yes - Payroll impact |
| Study Leave | ✅ Yes - Course verification |
Leave Type Display
In Leave Request List
Leave types display with their associated colour:
// Source: src/lib/adapters/__tests__/__snapshots__/leave-request.adapter.snapshot.test.ts.snap:28-30
{
"leaveTypeColor": "#10B981",
"leaveTypeId": "type-annual",
"leaveTypeName": "Annual Leave"
}
Adapter Transformation
The leave request adapter includes leave type details:
// LeaveRequestComponentType includes:
{
leaveTypeId: string
leaveTypeName: string
leaveTypeColor: string
}
UK Leave Entitlements
Statutory Minimum
UK employees are entitled to minimum annual leave:
| Employment Type | Minimum Days |
|---|---|
| Full-time | 28 days (5.6 weeks) including bank holidays |
| Part-time | Pro-rata based on hours worked |
Shyfts Default Configuration
// Source: src/services/ConfigService.ts:23-24
{
annual_leave_days: 25, // Excluding bank holidays
sick_leave_days: 10 // Statutory sick pay threshold
}
Bank Holiday Handling
Options for bank holiday inclusion:
| Approach | Configuration |
|---|---|
| Included in allocation | 28 days total entitlement |
| Separate from allocation | 25 days + 8 bank holidays |
Best Practices
Leave Type Configuration
- Clear Naming - Use descriptive names staff will understand
- Distinct Colours - Choose colours that are easily distinguishable
- Appropriate Allocations - Set days based on company policy
- Consider Carry-Over - Decide policy before year end
Carry-Over Recommendations
| Recommendation | Rationale |
|---|---|
| Cap at 5 days | Encourages staff to use leave |
| Q1 expiry | Prevents excessive accumulation |
| No carry-over for sick | Sick days shouldn't accumulate |
Approval Settings
| Leave Type | Recommendation |
|---|---|
| Planned leave | Require approval for scheduling |
| Emergency leave | Allow self-certification with manager notification |
| Long-term leave | Always require approval |
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Leave type not showing | Not loaded for company | Refresh leave types API |
| Wrong colour displaying | Type colour not set | Update leave type colour in settings |
| Balance not calculating | Missing type association | Ensure leave requests have valid type ID |
| Carry-over not working | Setting disabled | Enable allow_carry_over for type |
Validation Errors
| Error | Cause | Solution |
|---|---|---|
| "Invalid leave type ID" | UUID format error | Check leave type exists |
| "Leave type required" | Missing selection | Select a leave type |
| "Type not found" | Deleted or inactive | Use active leave type |
Related Documentation
- Approving Leave - Leave approval workflow
- Leave Reports - Leave reporting and analytics
- Staff Portal: Leave Requests - Staff guide to requesting leave
Source Files:
src/types/database.types.ts:925-963- Leave types database schemasrc/lib/db/leave-request.db.ts:685-700- Get leave types functionsrc/lib/validation/leave-request.schemas.ts- Leave validation schemassrc/hooks/useLeaveBalance.ts- Leave balance hooksrc/components/staff/StaffLeaveRequests.tsx- Leave request formsrc/services/ConfigService.ts- Default leave configuration