Leave Reports
This guide covers the leave analysis reporting features in Shyfts, including accessing reports, understanding metrics, filtering data, and exporting leave information.
Overview
Leave Analysis Report
The Leave Analysis report provides comprehensive insights into staff leave patterns:
- Leave Summary - Total days taken, approved, pending, rejected
- Leave Breakdown - Days by leave type
- Leave Patterns - Trends and seasonal patterns
- Staff Comparison - Leave usage across team members
Report Type Definition
// Source: src/types/reporting.ts:17-22
export type ReportType =
| 'STAFF_PERFORMANCE'
| 'SCHEDULING_ANALYTICS'
| 'ATTENDANCE_SUMMARY'
| 'HOURS_TRACKING'
| 'LEAVE_ANALYSIS' // Leave reports
| 'SKILLS_MATRIX'
| 'COST_ANALYSIS'
| 'COMPLIANCE_REPORT'
Accessing Leave Reports
From Reporting Dashboard
- Navigate to Dashboard → Reports
- Select the Leave tab from the report types
// Source: src/components/reporting/ReportingDashboard.tsx:491-496
const tabs = [
{ id: 'overview', label: 'Overview', icon: BarChart3 },
{ id: 'performance', label: 'Performance', icon: TrendingUp },
{ id: 'attendance', label: 'Attendance', icon: Clock },
{ id: 'leave', label: 'Leave', icon: Calendar },
{ id: 'cost', label: 'Cost', icon: DollarSign },
{ id: 'compliance', label: 'Compliance', icon: ShieldCheck }
]
Report Header
// Source: src/components/reporting/ReportingDashboard.tsx:893-894
<h3 className="text-xl font-semibold text-white">Leave Analysis</h3>
Leave Analysis Data Structure
LeaveAnalysisData Interface
// Source: src/types/reporting.ts:306-335
export interface LeaveAnalysisData {
staff_id: string
staff_name: string
role: string
leave_summary: {
total_leave_days: number
approved_leave_days: number
pending_requests: number
rejected_requests: number
leave_balance: number
leave_utilization_rate: number
}
leave_breakdown: Array<{
leave_type: string
days_taken: number
days_remaining: number
utilization_percentage: number
}>
leave_patterns: {
most_common_leave_type: string
average_leave_duration: number
seasonal_trends: Array<{
month: string
leave_requests: number
}>
}
leave_taken_by_type: {
[key: string]: number
}
}
Report Metrics
Leave Summary Metrics
| Metric | Description | Source |
|---|---|---|
| Total Leave Days | All leave days in period | Sum of all leave requests |
| Approved Days | Days with approved status | Approved leave requests |
| Pending Requests | Requests awaiting approval | Status = 'pending' |
| Rejected Requests | Declined requests | Status = 'rejected' |
| Leave Balance | Days remaining | Entitlement - Used - Pending |
| Utilisation Rate | Percentage of allowance used | (Used / Entitlement) x 100 |
Summary Calculation
// Source: src/services/reportingService.ts:1007-1014
leave_summary: {
total_leave_days: Object.values(leaveByType).reduce((sum, data) => sum + data.taken, 0),
approved_leave_days: Object.values(leaveByType).reduce((sum, data) => sum + data.approved, 0),
pending_requests: leaveRequests?.filter(r => r.status === 'pending').length || 0,
rejected_requests: leaveRequests?.filter(r => r.status === 'rejected').length || 0,
leave_balance: 0, // Calculated from entitlement data
leave_utilization_rate: 0
}
Report Display
Leave Analysis Table
The report displays in a data table format:
// Source: src/components/reporting/ReportingDashboard.tsx:907-926
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/20">
<th className="text-left py-3 px-4 text-white font-semibold">Staff Name</th>
<th className="text-right py-3 px-4 text-white font-semibold">Approved Days</th>
<th className="text-right py-3 px-4 text-white font-semibold">Pending</th>
<th className="text-right py-3 px-4 text-white font-semibold">Rejected</th>
</tr>
</thead>
<tbody>
{leaveData.map((row) => (
<tr key={row.staff_id} className="border-b border-white/10 hover:bg-white/5">
<td className="py-3 px-4 text-white">{row.staff_name}</td>
<td className="py-3 px-4 text-right text-white">{row.leave_summary.approved_leave_days}</td>
<td className="py-3 px-4 text-right text-white">{row.leave_summary.pending_requests}</td>
<td className="py-3 px-4 text-right text-white">{row.leave_summary.rejected_requests}</td>
</tr>
))}
</tbody>
</table>
Table Columns
| Column | Description |
|---|---|
| Staff Name | Employee full name |
| Approved Days | Number of approved leave days |
| Pending | Count of pending requests |
| Rejected | Count of rejected requests |
Empty State
// Source: src/components/reporting/ReportingDashboard.tsx:929-936
{leaveData.length === 0 && (
<div className="text-center py-8">
<div className="text-secondary-text mb-2">No leave data available</div>
<div className="text-sm text-white/60">
Try expanding the date range or clearing filters.
</div>
</div>
)}
Date Range Selection
Period Types
Reports can be generated for various date ranges:
// Source: src/types/reporting.ts:27-31
export interface DateRange {
start_date: string
end_date: string
period_type: 'DAY' | 'WEEK' | 'MONTH' | 'QUARTER' | 'YEAR' | 'CUSTOM'
}
Common Date Ranges
| Period | Description |
|---|---|
| Day | Single day report |
| Week | 7-day period |
| Month | Calendar month |
| Quarter | 3-month period |
| Year | Full year (often calendar or financial year) |
| Custom | User-defined start and end dates |
Filtering Reports
Available Filters
// Source: src/types/reporting.ts:59-67
export interface ReportFilters {
staff_ids?: string[] // Filter by specific staff
role_ids?: string[] // Filter by role
employment_types?: string[] // Filter by employment type
leave_type_ids?: string[] // Filter by leave type
}
Leave Type Filter
Filter reports by specific leave types:
// Source: src/types/reporting.ts:64
leave_type_ids?: string[] // leave_requests.leave_type_id → leave_types.id
Example Filter Usage
{
"staff_ids": ["staff-uuid-1", "staff-uuid-2"],
"leave_type_ids": ["annual-leave-uuid"]
}
Pagination
Pagination State
// Source: src/components/reporting/ReportingDashboard.tsx:77-79
const [leavePage, setLeavePage] = useState(1)
const [leavePageSize, setLeavePageSize] = useState(50)
const [leavePagination, setLeavePagination] = useState<PaginationMeta | null>(null)
Pagination Controls
// Source: src/components/reporting/ReportingDashboard.tsx:895-903
{renderPaginationControls({
label: 'Leave',
pagination: leavePagination,
page: leavePage,
pageSize: leavePageSize,
setPage: setLeavePage,
setPageSize: setLeavePageSize
})}
Page Size Options
| Option | Records Per Page |
|---|---|
| 10 | Quick scan |
| 25 | Default view |
| 50 | Extended view |
| 100 | Full team view |
Data Fetching
Fetch Leave Data Function
// Source: src/components/reporting/ReportingDashboard.tsx:217-233
const fetchLeaveData = React.useCallback(async (options?: { cacheBypass?: boolean }) => {
try {
const result = await runReport<LeaveAnalysisData[]>('LEAVE_ANALYSIS', {
...options,
pagination: { page: leavePage, pageSize: leavePageSize }
})
setLeaveData(result.data)
setLeavePagination(result.pagination || null)
const charts = ReportingService.generateChartData(result.data, 'leave_analysis')
setChartData(prev => ({ ...prev, leave: charts }))
} catch (error) {
console.error('Failed to fetch leave analysis data:', error)
setLeaveData([])
setLeavePagination(null)
throw error
}
}, [runReport, leavePage, leavePageSize])
Report API Call
// Source: src/components/reporting/ReportingDashboard.tsx:99-105
const runReport = React.useCallback(
async <T,>(
reportType: ReportType,
options?: { cacheBypass?: boolean; pagination?: { page: number; pageSize: number } }
): Promise<{ data: T; pagination?: PaginationMeta }> => {
const response = await fetch('/api/reports/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
reportType,
companyId: user?.company_id,
dateRange,
filters,
pagination: options?.pagination,
cacheBypass: options?.cacheBypass
})
})
// ...
}
)
Leave Breakdown
Leave by Type
The report breaks down leave by type:
// Source: src/services/reportingService.ts:1001-1006
leave_breakdown: Object.entries(leaveByType).map(([type, data]) => ({
leave_type: type,
days_taken: data.taken,
days_remaining: 0, // Would need entitlement data
utilization_percentage: 0
}))
Breakdown Fields
| Field | Description |
|---|---|
| leave_type | Name of the leave type |
| days_taken | Days used for this type |
| days_remaining | Days left for this type |
| utilization_percentage | % of type allowance used |
Export Options
Available Export Formats
// Source: src/components/reporting/ReportingDashboard.tsx:1094-1114
<button onClick={() => handleExport('CSV')}>
Export as CSV
</button>
<button onClick={() => handleExport('PDF')}>
Export as PDF
</button>
<button onClick={() => handleExport('EXCEL')}>
Export as Excel
</button>
Export Formats
| Format | Use Case | File Extension |
|---|---|---|
| CSV | Data analysis, spreadsheet import | .csv |
| Printed reports, formal documentation | .pdf | |
| Excel | Advanced analysis, charts | .xlsx |
Export Function
// Source: src/components/reporting/ReportingDashboard.tsx:403-485
const handleExport = async (format: 'PDF' | 'EXCEL' | 'CSV') => {
setExportStatus(null)
let reportData: unknown
let reportTitle = ''
switch (activeTab) {
case 'leave':
reportData = leaveData
reportTitle = 'Leave_Analysis_Report'
break
// ... other cases
}
try {
const result = await ReportingService.exportReport(reportData, format, reportTitle)
if (result.success) {
setExportStatus({
success: true,
message: `Successfully exported as ${format}. File: ${result.file_name}`
})
}
} catch (error) {
setExportStatus({
success: false,
message: error instanceof Error ? error.message : 'Export failed'
})
}
}
Export Status Display
// Source: src/components/reporting/ReportingDashboard.tsx:1117-1129
{exportStatus && (
<div className={`mt-4 p-3 rounded-lg ${
exportStatus.success ? 'bg-green-500/20' : 'bg-red-500/20'
}`}>
<p className={`text-sm ${
exportStatus.success ? 'text-green-400' : 'text-red-400'
}`}>
{exportStatus.message}
</p>
</div>
)}
Report Generation
Server-Side Report Generation
// Source: src/services/reportingService.ts:884-892
static async generateLeaveAnalysisReport(
db: DbClient,
companyId: string,
dateRange: DateRange,
filters: ReportFilters = {}
): Promise<LeaveAnalysisData[]> {
const result = await this.generateLeaveAnalysisReportPaginated(
db, companyId, dateRange, filters
)
return result.data
}
Data Query
// Source: src/services/reportingService.ts:943-955
const { data: allLeaveRequestsRaw, error: leaveRequestsError } = await db
.from('leave_requests')
.select(`
*,
leave_types (*),
staff!staff_id (first_name, last_name),
approved_by_staff:staff!approved_by (first_name, last_name)
`)
.in('staff_id', staffIds)
.eq('company_id', companyId)
.lte('start_date', dateRange.end_date)
.gte('end_date', dateRange.start_date)
Sorting Results
Reports are sorted by total leave days (highest first):
// Source: src/services/reportingService.ts:1023-1027
const sorted = leaveData.sort((a, b) => {
const totalA = Object.values(a.leave_taken_by_type).reduce((sum, days) => sum + days, 0)
const totalB = Object.values(b.leave_taken_by_type).reduce((sum, days) => sum + days, 0)
return totalB - totalA
})
Performance Metrics Integration
Leave in Staff Performance
Leave data is also included in staff performance metrics:
// Source: src/types/reporting.ts:82-84
metrics: {
// ...
leave_days_taken: number
// ...
}
This allows managers to see leave usage alongside other performance indicators.
Chart Data Generation
Visual Analytics
Leave data can be visualised with charts:
// Source: src/components/reporting/ReportingDashboard.tsx:225-226
const charts = ReportingService.generateChartData(result.data, 'leave_analysis')
setChartData(prev => ({ ...prev, leave: charts }))
Potential Chart Types
| Chart | Shows |
|---|---|
| Bar Chart | Leave days by staff member |
| Pie Chart | Leave breakdown by type |
| Line Chart | Leave trends over time |
| Stacked Bar | Leave types per staff member |
Report Caching
Cache Bypass
Reports can bypass cache for fresh data:
// Source: src/components/reporting/ReportingDashboard.tsx:890
<ReportErrorBoundary
title="Leave analysis failed to render"
onRetry={() => fetchReportData({ cacheBypass: true })}
>
When to Bypass Cache
| Scenario | Use Cache Bypass |
|---|---|
| After approving leave | ✅ Yes |
| After date range change | ❌ No (auto-refresh) |
| Suspected stale data | ✅ Yes |
| Regular viewing | ❌ No |
Error Handling
Error Boundary
// Source: src/components/reporting/ReportingDashboard.tsx:889-940
<ReportErrorBoundary
title="Leave analysis failed to render"
onRetry={() => fetchReportData({ cacheBypass: true })}
>
{/* Report content */}
</ReportErrorBoundary>
Error Logging
// Source: src/services/reportingService.ts:1031
logger.error('Failed to generate leave analysis report', error as Error, {
service: 'reportingService',
method: 'generateLeaveAnalysisReport'
})
Best Practices
Report Generation
- Choose appropriate date range - Longer ranges = more data to process
- Use filters - Narrow down to relevant staff or leave types
- Export regularly - Keep historical records
- Review trends - Look for patterns in leave usage
Data Analysis
- Compare periods - Year-on-year or quarter-on-quarter
- Identify high usage - Staff approaching limits
- Monitor pending - Requests awaiting approval
- Track rejections - May indicate policy issues
Compliance
- Track entitlements - Ensure staff take minimum leave
- Monitor balances - Identify staff not using leave
- Year-end review - Check carry-over limits
- Audit trail - Export for records retention
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| No data showing | Date range too narrow | Expand date range |
| Missing staff | Filter applied | Clear staff filters |
| Export fails | Large dataset | Reduce date range or filter |
| Slow loading | Many records | Use pagination, reduce page size |
Performance Tips
| Tip | Benefit |
|---|---|
| Use filters | Reduces data volume |
| Smaller date ranges | Faster queries |
| Paginate results | Better responsiveness |
| Cache bypass sparingly | Reduces server load |
Related Documentation
- Approving Leave - Leave approval workflow
- Leave Types - Leave type configuration
- Reports Overview - All available reports
- Exporting Data - Export formats and options
Source Files:
src/types/reporting.ts:306-335- LeaveAnalysisData interfacesrc/services/reportingService.ts:884-1033- Leave report generationsrc/components/reporting/ReportingDashboard.tsx:217-233- Leave data fetchingsrc/components/reporting/ReportingDashboard.tsx:889-940- Leave report displaysrc/app/api/reports/run/route.ts- Report API endpoint