Skip to main content

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:

  1. Annual Allocation - Days entitled per year
  2. Colour Coding - Visual identification throughout the system
  3. Carry-Over Rules - Whether unused days roll over to next year
  4. 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 TypeColourDefault DaysDescription
Annual Leave#3B82F6 (Blue)25-28Yearly vacation entitlement
Sick Leave#EF4444 (Red)10Medical-related absence
Personal Leave#8B5CF6 (Purple)3-5Personal 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

PropertyTypeRequiredDescription
nameString✅ YesDisplay name of the leave type
descriptionString❌ NoAdditional details about the leave type
annual_allocation_daysNumber✅ YesDays allocated per year (default: varies)
colorHex String❌ NoVisual colour for calendar/badges
requires_approvalBoolean❌ NoWhether manager approval is required

Carry-Over Settings

PropertyTypeDefaultDescription
allow_carry_overBooleanfalseWhether unused days can be carried to next year
max_carry_over_daysNumbernullMaximum 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 TypeHex CodeVisual
Annual Leave#3B82F6Blue
Annual Leave (Alt)#10B981Green
Sick Leave#EF4444Red
Personal Leave#8B5CF6Purple
Compassionate#EC4899Pink
Maternity/Paternity#F59E0BAmber
Study Leave#06B6D4Cyan
Unpaid Leave#6B7280Grey

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

FieldCalculation
entitlement_daysannual_allocation_days + carry_over_days
used_daysSum of approved leave requests in current year
pending_daysSum of pending leave requests
available_daysentitlement_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

SettingBehaviour
allow_carry_over = falseUnused days expire at year end
allow_carry_over = trueUnused days roll to next year
max_carry_over_days = 5Maximum 5 days can be carried
max_carry_over_days = nullUnlimited carry-over (if allowed)

Carry-Over Example

Scenario: Staff member has 28 days annual leave, uses 25 days

SettingResult
No carry-over3 days lost at year end
Carry-over, max 53 days carry to next year
Carry-over, unlimitedAll 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

SettingBehaviour
requires_approval = trueRequest goes to manager for approval
requires_approval = falseRequest auto-approved (or no approval needed)

Common Configurations

Leave TypeTypically 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 TypeMinimum Days
Full-time28 days (5.6 weeks) including bank holidays
Part-timePro-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:

ApproachConfiguration
Included in allocation28 days total entitlement
Separate from allocation25 days + 8 bank holidays

Best Practices

Leave Type Configuration

  1. Clear Naming - Use descriptive names staff will understand
  2. Distinct Colours - Choose colours that are easily distinguishable
  3. Appropriate Allocations - Set days based on company policy
  4. Consider Carry-Over - Decide policy before year end

Carry-Over Recommendations

RecommendationRationale
Cap at 5 daysEncourages staff to use leave
Q1 expiryPrevents excessive accumulation
No carry-over for sickSick days shouldn't accumulate

Approval Settings

Leave TypeRecommendation
Planned leaveRequire approval for scheduling
Emergency leaveAllow self-certification with manager notification
Long-term leaveAlways require approval

Troubleshooting

Common Issues

IssueCauseSolution
Leave type not showingNot loaded for companyRefresh leave types API
Wrong colour displayingType colour not setUpdate leave type colour in settings
Balance not calculatingMissing type associationEnsure leave requests have valid type ID
Carry-over not workingSetting disabledEnable allow_carry_over for type

Validation Errors

ErrorCauseSolution
"Invalid leave type ID"UUID format errorCheck leave type exists
"Leave type required"Missing selectionSelect a leave type
"Type not found"Deleted or inactiveUse active leave type


Source Files:

  • src/types/database.types.ts:925-963 - Leave types database schema
  • src/lib/db/leave-request.db.ts:685-700 - Get leave types function
  • src/lib/validation/leave-request.schemas.ts - Leave validation schemas
  • src/hooks/useLeaveBalance.ts - Leave balance hook
  • src/components/staff/StaffLeaveRequests.tsx - Leave request form
  • src/services/ConfigService.ts - Default leave configuration