Spaces:
Sleeping
Reports API - Frontend Documentation
Quick Start
// 1. Generate a report
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'custom',
date_range: {
start_date: '2025-12-01',
end_date: '2025-12-31'
},
custom_config: {
entity: 'tickets',
columns: ['status', 'priority'],
aggregations: [{ field: 'id', type: 'count', alias: 'count' }],
group_by: ['status', 'priority']
},
project_id: 'your-project-id'
})
});
const data = await response.json();
// Use data.data for your charts/tables
Overview
The Reports API provides business intelligence reports with pre-calculated metrics and aggregations. Use this for dashboard analytics, performance tracking, and compliance monitoring.
Base URL: /api/v1/reports
Authorization:
view_reportspermission required for viewingexport_reportspermission required for CSV downloads- Available to: PLATFORM_ADMIN, PROJECT_MANAGER, DISPATCHER, SALES_MANAGER
Key Features:
- β Pre-defined reports (SLA, Performance, Financial, Inventory)
- β Custom reports with flexible configuration
- β Calculated fields (SLA violations, days to complete)
- β Aggregations (count, sum, avg, min, max)
- β CSV export for Excel
- β Real-time data (no caching)
TypeScript Interfaces
Copy these into your frontend project:
// ============================================
// REQUEST TYPES
// ============================================
type ReportType =
| 'sla_compliance'
| 'user_performance'
| 'financial_summary'
| 'inventory_usage'
| 'custom';
type AggregationType = 'count' | 'sum' | 'avg' | 'min' | 'max';
interface DateRange {
start_date: string; // ISO date: "2025-12-01"
end_date: string; // ISO date: "2025-12-31"
}
interface AggregationConfig {
field: string;
type: AggregationType;
alias?: string;
}
interface CustomReportConfig {
entity: 'tickets' | 'timesheets' | 'payroll' | 'expenses' | 'users';
columns: string[];
calculated_fields?: string[];
aggregations?: AggregationConfig[];
group_by?: string[];
additional_filters?: Record<string, any>;
}
interface ReportRequest {
report_type: ReportType;
date_range: DateRange;
project_id?: string;
region_id?: string;
user_id?: string;
status?: string;
limit?: number;
skip?: number;
custom_config?: CustomReportConfig; // Required when report_type = 'custom'
}
// ============================================
// RESPONSE TYPES
// ============================================
interface ReportMeta {
generated_at: string;
record_count: number;
filters_applied: Record<string, any>;
report_type: string;
}
interface ReportResponse<T = any> {
meta: ReportMeta;
data: T[];
}
// Specific row types
interface SLAReportRow {
ticket_id: string;
ticket_number: string;
project_name: string;
ticket_type: string;
priority: string;
status: string;
created_at: string;
due_date: string | null;
completed_at: string | null;
is_violated: boolean;
violation_margin_hours: number;
assigned_agent: string;
customer_name: string;
}
interface UserPerformanceRow {
user_id: string;
user_name: string;
role: string;
tickets_assigned: number;
tickets_completed: number;
completion_rate: number;
total_hours_logged: number;
avg_resolution_time_hours: number;
sla_violations_count: number;
on_time_percentage: number;
}
interface FinancialReportRow {
category: string;
description: string;
date: string;
project_name: string;
amount: number;
status: string;
transaction_type: 'Credit' | 'Debit';
}
interface InventoryUsageRow {
item_name: string;
serial_number: string | null;
category: string;
used_at: string;
ticket_id: string;
project_name: string;
used_by_user: string;
unit_cost: number;
}
// Custom report rows are dynamic
type CustomReportRow = Record<string, any>;
Endpoints
1. Generate Report (JSON)
Returns report data as JSON for rendering in tables, charts, or dashboards.
POST /api/v1/reports/generate
Headers:
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body:
{
report_type: "sla_compliance" | "user_performance" | "financial_summary" | "inventory_usage";
date_range: {
start_date: string; // ISO date: "2025-12-01"
end_date: string; // ISO date: "2025-12-31"
};
project_id?: string; // UUID - optional filter
region_id?: string; // UUID - optional filter
user_id?: string; // UUID - optional filter
status?: string; // optional filter (e.g., "completed", "open")
limit?: number; // pagination limit (default: no limit)
skip?: number; // pagination offset (default: 0)
}
Response (200 OK):
{
meta: {
generated_at: string; // ISO timestamp
record_count: number; // total records returned
filters_applied: object; // echo of filters used
report_type: string; // report type
};
data: Array<ReportRow>; // varies by report type (see below)
}
Error Responses:
401 Unauthorized- Missing or invalid token403 Forbidden- User lacksview_reportspermission500 Internal Server Error- Report generation failed
2. Export Report (CSV Download)
Downloads report data as CSV file for Excel or external processing.
POST /api/v1/reports/export
Headers:
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body:
{
report_type: "sla_compliance" | "user_performance" | "financial_summary" | "inventory_usage";
date_range: {
start_date: string; // ISO date: "2025-12-01"
end_date: string; // ISO date: "2025-12-31"
};
format?: "csv"; // only CSV supported currently
project_id?: string; // UUID - optional filter
region_id?: string; // UUID - optional filter
user_id?: string; // UUID - optional filter
status?: string; // optional filter
limit?: number; // pagination limit
skip?: number; // pagination offset
}
Response (200 OK):
- Content-Type:
text/csv - Content-Disposition:
attachment; filename="{report_type}_{timestamp}.csv" - Body: CSV file with UTF-8 BOM encoding (Excel-compatible)
Error Responses:
400 Bad Request- Invalid format (only CSV supported)401 Unauthorized- Missing or invalid token403 Forbidden- User lacksexport_reportspermission500 Internal Server Error- Export failed
Report Types & Data Shapes
1. Custom Report (NEW - Fully Configurable)
Purpose: Generate any report with flexible column selection, calculated fields, and aggregations
Report Type: "custom"
Use Cases:
- Ad-hoc data analysis
- Custom dashboard widgets
- Flexible reporting for any entity
- Aggregated statistics by any dimension
Configuration:
{
report_type: "custom",
date_range: {
start_date: "2025-12-01",
end_date: "2025-12-31"
},
custom_config: {
entity: "tickets" | "timesheets" | "payroll" | "expenses" | "users",
columns: string[], // Fields to include
calculated_fields?: string[], // "sla_violated", "days_to_complete", "is_overdue"
aggregations?: Array<{
field: string,
type: "count" | "sum" | "avg" | "min" | "max",
alias?: string
}>,
group_by?: string[], // Group results by these fields
additional_filters?: Record<string, any> // Entity-specific filters
},
project_id?: string,
region_id?: string,
user_id?: string
}
Example 1: Tickets by Status with Counts
{
"report_type": "custom",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-31"
},
"custom_config": {
"entity": "tickets",
"columns": ["status", "priority"],
"aggregations": [
{
"field": "id",
"type": "count",
"alias": "ticket_count"
}
],
"group_by": ["status", "priority"]
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a"
}
Response:
{
"meta": {
"generated_at": "2025-12-14T20:00:00Z",
"record_count": 6,
"filters_applied": {...},
"report_type": "custom"
},
"data": [
{
"status": "completed",
"priority": "high",
"ticket_count": 15
},
{
"status": "completed",
"priority": "medium",
"ticket_count": 23
},
{
"status": "open",
"priority": "high",
"ticket_count": 8
}
]
}
Example 2: Detailed Ticket List with Calculated Fields
{
"report_type": "custom",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-31"
},
"custom_config": {
"entity": "tickets",
"columns": [
"id",
"ticket_name",
"status",
"priority",
"created_at",
"completed_at"
],
"calculated_fields": [
"sla_violated",
"days_to_complete",
"is_overdue"
],
"additional_filters": {
"ticket_type": "installation"
}
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a",
"limit": 50
}
Response:
{
"meta": {
"generated_at": "2025-12-14T20:00:00Z",
"record_count": 50,
"filters_applied": {...},
"report_type": "custom"
},
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ticket_name": "Install fiber at ABC Corp",
"status": "completed",
"priority": "high",
"created_at": "2025-12-01T10:00:00Z",
"completed_at": "2025-12-04T15:30:00Z",
"sla_violated": false,
"days_to_complete": 3,
"is_overdue": false
}
]
}
Example 3: Payroll Summary by User
{
"report_type": "custom",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-31"
},
"custom_config": {
"entity": "payroll",
"columns": ["user_id"],
"aggregations": [
{
"field": "total_amount",
"type": "sum",
"alias": "total_paid"
},
{
"field": "days_worked",
"type": "sum",
"alias": "total_days"
},
{
"field": "tickets_closed",
"type": "sum",
"alias": "total_tickets"
}
],
"group_by": ["user_id"]
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a"
}
Response:
{
"meta": {
"generated_at": "2025-12-14T20:00:00Z",
"record_count": 12,
"filters_applied": {...},
"report_type": "custom"
},
"data": [
{
"user_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"total_paid": 45000.0,
"total_days": 20,
"total_tickets": 35
}
]
}
Supported Entities:
tickets- Work orders/ticketstimesheets- Daily attendance and work logspayroll- Worker compensation recordsexpenses- Ticket expensesusers- User records
Supported Calculated Fields:
sla_violated- Whether SLA was breached (tickets)days_to_complete- Days from creation to completion (tickets)is_overdue- Whether ticket is overdue (tickets)
Supported Aggregations:
count- Count recordssum- Sum numeric fieldavg- Average of numeric fieldmin- Minimum valuemax- Maximum value
UI Implementation Tips:
- Use for custom dashboard widgets
- Allow users to build their own reports
- Combine with date range picker for flexible analysis
- Use aggregations for summary statistics
- Use detailed mode (no aggregations) for drill-down
2. SLA Compliance Report
Purpose: Track ticket completion vs due dates, identify SLA violations
Report Type: "sla_compliance"
Use Cases:
- Dashboard SLA metrics
- Overdue ticket alerts
- Performance monitoring
- Customer satisfaction tracking
Data Shape:
interface SLAReportRow {
ticket_id: string; // UUID
ticket_number: string; // Display ID (e.g., "TKT-12345")
project_name: string; // Project title
ticket_type: string; // "installation" | "support" | "infrastructure"
priority: string; // "low" | "medium" | "high" | "urgent"
status: string; // "open" | "in_progress" | "completed" | etc.
created_at: string; // ISO timestamp
due_date: string | null; // ISO timestamp
completed_at: string | null; // ISO timestamp
is_violated: boolean; // true if SLA was breached
violation_margin_hours: number; // negative = early, positive = late
assigned_agent: string; // Agent name or "Unassigned"
customer_name: string; // Customer name or "Internal/Infrastructure"
}
Example Response:
{
"meta": {
"generated_at": "2025-12-14T18:30:00Z",
"record_count": 3,
"filters_applied": {
"report_type": "sla_compliance",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a"
},
"report_type": "sla_compliance"
},
"data": [
{
"ticket_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ticket_number": "TKT-12345",
"project_name": "Atomio FTTX",
"ticket_type": "installation",
"priority": "high",
"status": "completed",
"created_at": "2025-12-01T10:00:00Z",
"due_date": "2025-12-05T17:00:00Z",
"completed_at": "2025-12-04T15:30:00Z",
"is_violated": false,
"violation_margin_hours": -25.5,
"assigned_agent": "John Doe",
"customer_name": "ABC Corporation"
},
{
"ticket_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"ticket_number": "TKT-12346",
"project_name": "Atomio FTTX",
"ticket_type": "support",
"priority": "urgent",
"status": "completed",
"created_at": "2025-12-02T09:00:00Z",
"due_date": "2025-12-03T17:00:00Z",
"completed_at": "2025-12-04T10:00:00Z",
"is_violated": true,
"violation_margin_hours": 17.0,
"assigned_agent": "Jane Smith",
"customer_name": "XYZ Ltd"
}
]
}
UI Implementation Tips:
- Show
is_violated: truetickets in red/warning color - Display
violation_margin_hoursas "X hours late" (positive) or "X hours early" (negative) - Filter by
priorityfor urgent SLA violations - Sort by
violation_margin_hoursdescending to show worst violations first
2. User Performance Report
Purpose: Aggregate worker performance metrics from timesheets
Report Type: "user_performance"
Use Cases:
- Team performance dashboards
- Individual worker reviews
- Productivity tracking
- Resource allocation planning
Data Shape:
interface UserPerformanceRow {
user_id: string; // UUID
user_name: string; // Full name
role: string; // "field_agent" | "project_manager" | etc.
tickets_assigned: number; // Total tickets assigned
tickets_completed: number; // Total tickets completed
completion_rate: number; // Percentage (0-100)
total_hours_logged: number; // Total hours from timesheets
avg_resolution_time_hours: number; // Average hours per ticket
sla_violations_count: number; // Number of SLA breaches
on_time_percentage: number; // Percentage completed on time (0-100)
}
Example Response:
{
"meta": {
"generated_at": "2025-12-14T18:30:00Z",
"record_count": 2,
"filters_applied": {
"report_type": "user_performance",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
}
},
"report_type": "user_performance"
},
"data": [
{
"user_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"user_name": "John Doe",
"role": "field_agent",
"tickets_assigned": 25,
"tickets_completed": 23,
"completion_rate": 92.0,
"total_hours_logged": 184.5,
"avg_resolution_time_hours": 8.02,
"sla_violations_count": 2,
"on_time_percentage": 91.3
},
{
"user_id": "d4e5f6a7-b8c9-0123-def1-234567890123",
"user_name": "Jane Smith",
"role": "field_agent",
"tickets_assigned": 30,
"tickets_completed": 28,
"completion_rate": 93.33,
"total_hours_logged": 210.0,
"avg_resolution_time_hours": 7.5,
"sla_violations_count": 1,
"on_time_percentage": 96.43
}
]
}
UI Implementation Tips:
- Show
completion_rateas progress bar or percentage badge - Highlight users with
on_time_percentage < 90%in warning color - Display
avg_resolution_time_hoursas "X.X hours per ticket" - Sort by
completion_rateoron_time_percentagefor leaderboards - Use
sla_violations_countfor quality metrics
3. Financial Summary Report
Purpose: Unified ledger of revenue (ProjectFinance) and expenses (TicketExpenses)
Report Type: "financial_summary"
Use Cases:
- Project financial tracking
- Budget monitoring
- Expense approval workflows
- Revenue vs cost analysis
Data Shape:
interface FinancialReportRow {
category: string; // "General" | "Field Expense" | custom
description: string; // Transaction description
date: string; // ISO date (YYYY-MM-DD)
project_name: string; // Project title
amount: number; // Transaction amount (positive number)
status: string; // "Approved" | "Pending" | "Paid" | etc.
transaction_type: "Credit" | "Debit"; // Credit = revenue, Debit = expense
}
Example Response:
{
"meta": {
"generated_at": "2025-12-14T18:30:00Z",
"record_count": 4,
"filters_applied": {
"report_type": "financial_summary",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a"
},
"report_type": "financial_summary"
},
"data": [
{
"category": "General",
"description": "Client payment - Invoice #INV-001",
"date": "2025-12-01",
"project_name": "Atomio FTTX",
"amount": 150000.0,
"status": "Paid",
"transaction_type": "Credit"
},
{
"category": "Field Expense",
"description": "transport: Fuel for site visit",
"date": "2025-12-02",
"project_name": "Atomio FTTX",
"amount": 2500.0,
"status": "Approved",
"transaction_type": "Debit"
},
{
"category": "Field Expense",
"description": "materials: Fiber optic cable",
"date": "2025-12-03",
"project_name": "Atomio FTTX",
"amount": 15000.0,
"status": "Approved",
"transaction_type": "Debit"
}
]
}
UI Implementation Tips:
- Show
transaction_type: "Credit"in green,"Debit"in red - Calculate running balance:
balance += (Credit - Debit) - Group by
categoryfor expense breakdown charts - Filter by
statusto show pending approvals - Sort by
datedescending for recent transactions first - Sum
amountbytransaction_typefor totals
4. Inventory Usage Report
Purpose: Track inventory items installed/consumed across tickets
Report Type: "inventory_usage"
Use Cases:
- Inventory consumption tracking
- Cost analysis per project
- Equipment deployment monitoring
- Stock replenishment planning
Data Shape:
interface InventoryUsageRow {
item_name: string; // Inventory item name
serial_number: string | null; // Serial number if tracked
category: string; // "General" | custom category
used_at: string; // ISO timestamp
ticket_id: string; // UUID of ticket where used
project_name: string; // Project title
used_by_user: string; // User name or "Unknown"
unit_cost: number; // Cost per unit
}
Example Response:
{
"meta": {
"generated_at": "2025-12-14T18:30:00Z",
"record_count": 3,
"filters_applied": {
"report_type": "inventory_usage",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
},
"project_id": "0ade6bd1-e492-4e25-b681-59f42058d29a"
},
"report_type": "inventory_usage"
},
"data": [
{
"item_name": "ONT Device - Model X200",
"serial_number": "ONT-2025-001234",
"category": "Customer Equipment",
"used_at": "2025-12-02T14:30:00Z",
"ticket_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"project_name": "Atomio FTTX",
"used_by_user": "John Doe",
"unit_cost": 5500.0
},
{
"item_name": "Fiber Optic Cable (100m)",
"serial_number": null,
"category": "Installation Materials",
"used_at": "2025-12-03T10:15:00Z",
"ticket_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"project_name": "Atomio FTTX",
"used_by_user": "Jane Smith",
"unit_cost": 12000.0
}
]
}
UI Implementation Tips:
- Group by
categoryfor inventory breakdown - Sum
unit_costfor total inventory value used - Link
ticket_idto ticket detail pages - Show
serial_numberfor trackable equipment - Filter by
used_by_userfor individual usage tracking - Sort by
used_atdescending for recent usage
Choosing Between Report Types
Use Pre-defined Reports When:
- β You need standard business metrics (SLA compliance, user performance)
- β You want consistent reporting across the organization
- β You need optimized queries for common use cases
- β You want pre-calculated fields and complex joins
Pre-defined Reports:
sla_compliance- Ticket SLA trackinguser_performance- Worker productivity metricsfinancial_summary- Revenue and expenses ledgerinventory_usage- Equipment deployment tracking
Use Custom Reports When:
- β You need flexible column selection
- β You want to analyze data by custom dimensions
- β You need aggregations (count, sum, avg) by any field
- β You want to build ad-hoc reports or custom dashboards
- β Pre-defined reports don't meet your specific needs
Custom Report Advantages:
- Fully configurable columns
- Any entity (tickets, timesheets, payroll, expenses, users)
- Flexible aggregations and grouping
- Calculated fields on demand
- Entity-specific filtering
Common Filters
All report types support these optional filters:
interface ReportFilters {
project_id?: string; // Filter by specific project (UUID)
region_id?: string; // Filter by project region (UUID)
user_id?: string; // Filter by specific user (UUID)
status?: string; // Filter by status (varies by report type)
limit?: number; // Pagination: max records to return
skip?: number; // Pagination: records to skip
}
Filter Behavior:
- All filters are optional (omit for all data)
- Multiple filters are combined with AND logic
limitandskipenable pagination for large datasets- Managers automatically see only their project data (authorization-filtered)
Error Handling
Common Error Response Format:
{
detail: string; // Human-readable error message
}
Error Scenarios:
401 Unauthorized:
{
"detail": "Not authenticated"
}
403 Forbidden:
{
"detail": "User does not have permission to view reports"
}
500 Internal Server Error:
{
"detail": "Report generation failed: [error details]"
}
Frontend Implementation Guide
React Hook Example
// hooks/useReports.ts
import { useState } from 'react';
import { ReportRequest, ReportResponse } from '@/types/reports';
export const useReports = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const generateReport = async <T = any>(
request: ReportRequest
): Promise<ReportResponse<T> | null> => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || 'Failed to generate report');
}
const data = await response.json();
return data;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
return null;
} finally {
setLoading(false);
}
};
const exportReport = async (request: ReportRequest) => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/v1/reports/export', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error('Failed to export report');
}
// Trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `report_${Date.now()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
return { generateReport, exportReport, loading, error };
};
React Component Example
// components/TicketStatusChart.tsx
import { useEffect, useState } from 'react';
import { useReports } from '@/hooks/useReports';
import { CustomReportRow } from '@/types/reports';
interface Props {
projectId: string;
startDate: string;
endDate: string;
}
export const TicketStatusChart: React.FC<Props> = ({
projectId,
startDate,
endDate
}) => {
const { generateReport, loading, error } = useReports();
const [data, setData] = useState<CustomReportRow[]>([]);
useEffect(() => {
const fetchData = async () => {
const result = await generateReport<CustomReportRow>({
report_type: 'custom',
date_range: { start_date: startDate, end_date: endDate },
project_id: projectId,
custom_config: {
entity: 'tickets',
columns: ['status', 'priority'],
aggregations: [
{ field: 'id', type: 'count', alias: 'count' }
],
group_by: ['status', 'priority']
}
});
if (result) {
setData(result.data);
}
};
fetchData();
}, [projectId, startDate, endDate]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h3>Tickets by Status</h3>
{data.map((row, idx) => (
<div key={idx}>
{row.status} ({row.priority}): {row.count} tickets
</div>
))}
</div>
);
};
Vue Composable Example
// composables/useReports.ts
import { ref } from 'vue';
import type { ReportRequest, ReportResponse } from '@/types/reports';
export const useReports = () => {
const loading = ref(false);
const error = ref<string | null>(null);
const generateReport = async <T = any>(
request: ReportRequest
): Promise<ReportResponse<T> | null> => {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || 'Failed to generate report');
}
return await response.json();
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
return null;
} finally {
loading.value = false;
}
};
const exportReport = async (request: ReportRequest) => {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/v1/reports/export', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error('Failed to export report');
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `report_${Date.now()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading.value = false;
}
};
return { generateReport, exportReport, loading, error };
};
API Service Class (Framework Agnostic)
// services/reportsService.ts
import { ReportRequest, ReportResponse } from '@/types/reports';
class ReportsService {
private baseUrl = '/api/v1/reports';
private getHeaders() {
return {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
'Content-Type': 'application/json'
};
}
async generate<T = any>(request: ReportRequest): Promise<ReportResponse<T>> {
const response = await fetch(`${this.baseUrl}/generate`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(request)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to generate report');
}
return response.json();
}
async export(request: ReportRequest): Promise<void> {
const response = await fetch(`${this.baseUrl}/export`, {
method: 'POST',
headers: this.getHeaders(),
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error('Failed to export report');
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `report_${Date.now()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
// Helper: Generate SLA report
async getSLAReport(projectId: string, startDate: string, endDate: string) {
return this.generate({
report_type: 'sla_compliance',
date_range: { start_date: startDate, end_date: endDate },
project_id: projectId
});
}
// Helper: Generate custom aggregation report
async getAggregationReport(
entity: string,
columns: string[],
aggregations: any[],
groupBy: string[],
filters: any
) {
return this.generate({
report_type: 'custom',
date_range: filters.date_range,
project_id: filters.project_id,
custom_config: {
entity,
columns,
aggregations,
group_by: groupBy
}
});
}
}
export const reportsService = new ReportsService();
1. Report Dashboard Page
// Example: Fetch SLA Compliance Report
const fetchSLAReport = async (projectId: string, startDate: string, endDate: string) => {
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'sla_compliance',
date_range: {
start_date: startDate,
end_date: endDate
},
project_id: projectId
})
});
if (!response.ok) {
throw new Error('Failed to fetch report');
}
return await response.json();
};
2. CSV Export Button
// Example: Download CSV Export
const downloadReport = async (reportType: string, filters: any) => {
const response = await fetch('/api/v1/reports/export', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: reportType,
...filters
})
});
if (!response.ok) {
throw new Error('Failed to export report');
}
// Trigger download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${reportType}_${new Date().toISOString()}.csv`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
};
3. Date Range Picker
// Example: Default to current week
const getDefaultDateRange = () => {
const today = new Date();
const monday = new Date(today);
monday.setDate(today.getDate() - today.getDay() + 1);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
return {
start_date: monday.toISOString().split('T')[0],
end_date: sunday.toISOString().split('T')[0]
};
};
4. Report Type Selector
const reportTypes = [
{ value: 'sla_compliance', label: 'SLA Compliance', icon: 'β±οΈ' },
{ value: 'user_performance', label: 'User Performance', icon: 'π' },
{ value: 'financial_summary', label: 'Financial Summary', icon: 'π°' },
{ value: 'inventory_usage', label: 'Inventory Usage', icon: 'π¦' }
];
Performance Notes
- Reports are designed for sub-2 second response times
- Use
limitandskipfor pagination on large datasets - User Performance report uses aggregated timesheet data (fast)
- SLA Compliance may be slower for large date ranges (complex joins)
- Consider caching report results for frequently-accessed data
Custom Report Examples
Example 1: Ticket Status Dashboard
Goal: Show ticket counts by status and priority for current month
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'custom',
date_range: {
start_date: '2025-12-01',
end_date: '2025-12-31'
},
custom_config: {
entity: 'tickets',
columns: ['status', 'priority'],
aggregations: [
{ field: 'id', type: 'count', alias: 'count' }
],
group_by: ['status', 'priority']
},
project_id: projectId
})
});
// Use for stacked bar chart or heatmap
Example 2: Worker Performance Leaderboard
Goal: Rank workers by tickets completed this week
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'custom',
date_range: {
start_date: '2025-12-08',
end_date: '2025-12-14'
},
custom_config: {
entity: 'timesheets',
columns: ['user_id'],
aggregations: [
{ field: 'tickets_completed', type: 'sum', alias: 'total_completed' },
{ field: 'hours_worked', type: 'sum', alias: 'total_hours' }
],
group_by: ['user_id']
},
project_id: projectId
})
});
// Sort by total_completed descending for leaderboard
Example 3: Expense Analysis by Category
Goal: Breakdown expenses by type for budget tracking
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'custom',
date_range: {
start_date: '2025-12-01',
end_date: '2025-12-31'
},
custom_config: {
entity: 'expenses',
columns: ['expense_type'],
aggregations: [
{ field: 'total_cost', type: 'sum', alias: 'total_amount' },
{ field: 'id', type: 'count', alias: 'expense_count' }
],
group_by: ['expense_type'],
additional_filters: {
is_approved: true
}
},
project_id: projectId
})
});
// Use for pie chart or expense breakdown table
Example 4: Overdue Tickets Report
Goal: List all overdue tickets with details
const response = await fetch('/api/v1/reports/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
report_type: 'custom',
date_range: {
start_date: '2025-01-01',
end_date: '2025-12-31'
},
custom_config: {
entity: 'tickets',
columns: [
'id',
'ticket_name',
'priority',
'status',
'due_date',
'created_at'
],
calculated_fields: ['is_overdue', 'days_to_complete'],
additional_filters: {
status: 'open'
}
},
project_id: projectId
})
});
// Filter client-side for is_overdue === true
// Sort by due_date ascending
Testing
Test with cURL:
# Generate SLA Report
curl -X POST "http://localhost:7860/api/v1/reports/generate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"report_type": "sla_compliance",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
}
}'
# Generate Custom Report
curl -X POST "http://localhost:7860/api/v1/reports/generate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"report_type": "custom",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
},
"custom_config": {
"entity": "tickets",
"columns": ["status", "priority"],
"aggregations": [
{"field": "id", "type": "count", "alias": "count"}
],
"group_by": ["status", "priority"]
}
}'
# Export to CSV
curl -X POST "http://localhost:7860/api/v1/reports/export" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"report_type": "user_performance",
"date_range": {
"start_date": "2025-12-01",
"end_date": "2025-12-14"
}
}' \
--output report.csv
Questions?
Contact the backend team or check:
- API Swagger docs:
/docs - Source code:
src/app/api/v1/reports.py - Service logic:
src/app/services/report_service.py
Common Integration Patterns
Pattern 1: Dashboard Widget with Auto-Refresh
// Auto-refresh every 5 minutes
useEffect(() => {
const fetchData = async () => {
const result = await reportsService.generate({
report_type: 'custom',
date_range: { start_date: startDate, end_date: endDate },
project_id: projectId,
custom_config: {
entity: 'tickets',
columns: ['status'],
aggregations: [{ field: 'id', type: 'count', alias: 'count' }],
group_by: ['status']
}
});
setData(result.data);
};
fetchData();
const interval = setInterval(fetchData, 5 * 60 * 1000);
return () => clearInterval(interval);
}, [projectId, startDate, endDate]);
Pattern 2: Report Builder UI
interface ReportBuilderState {
entity: string;
selectedColumns: string[];
aggregations: AggregationConfig[];
groupBy: string[];
filters: Record<string, any>;
}
const ReportBuilder = () => {
const [config, setConfig] = useState<ReportBuilderState>({
entity: 'tickets',
selectedColumns: [],
aggregations: [],
groupBy: [],
filters: {}
});
const handleGenerate = async () => {
const result = await reportsService.generate({
report_type: 'custom',
date_range: config.filters.date_range,
project_id: config.filters.project_id,
custom_config: {
entity: config.entity,
columns: config.selectedColumns,
aggregations: config.aggregations,
group_by: config.groupBy
}
});
// Display result
};
return (
<div>
<EntitySelector value={config.entity} onChange={...} />
<ColumnSelector columns={config.selectedColumns} onChange={...} />
<AggregationBuilder aggregations={config.aggregations} onChange={...} />
<button onClick={handleGenerate}>Generate Report</button>
</div>
);
};
Pattern 3: Cached Reports with React Query
import { useQuery } from '@tanstack/react-query';
const useTicketStatusReport = (projectId: string, dateRange: DateRange) => {
return useQuery({
queryKey: ['report', 'ticket-status', projectId, dateRange],
queryFn: () => reportsService.generate({
report_type: 'custom',
date_range: dateRange,
project_id: projectId,
custom_config: {
entity: 'tickets',
columns: ['status', 'priority'],
aggregations: [{ field: 'id', type: 'count', alias: 'count' }],
group_by: ['status', 'priority']
}
}),
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000 // 10 minutes
});
};
// Usage
const { data, isLoading, error } = useTicketStatusReport(projectId, dateRange);
Pattern 4: Export with Loading State
const ExportButton = ({ reportConfig }: { reportConfig: ReportRequest }) => {
const [exporting, setExporting] = useState(false);
const handleExport = async () => {
setExporting(true);
try {
await reportsService.export(reportConfig);
toast.success('Report exported successfully');
} catch (error) {
toast.error('Failed to export report');
} finally {
setExporting(false);
}
};
return (
<button onClick={handleExport} disabled={exporting}>
{exporting ? 'Exporting...' : 'Export to CSV'}
</button>
);
};
Troubleshooting
Issue: 403 Forbidden
Cause: User lacks view_reports or export_reports permission
Solution: Check user role - only PLATFORM_ADMIN, PROJECT_MANAGER, DISPATCHER, SALES_MANAGER have access
Issue: 422 Validation Error
Cause: Invalid request body (missing required fields, wrong date format)
Solution:
- Ensure
date_rangehas bothstart_dateandend_date - Use ISO date format: "YYYY-MM-DD"
- For custom reports, ensure
custom_configis provided
Issue: 500 Internal Server Error
Cause: Report generation failed (database error, invalid entity, etc.)
Solution:
- Check entity name is valid (tickets, timesheets, payroll, expenses, users)
- Verify column names exist for the entity
- Check aggregation field names are valid
- Review browser console for detailed error message
Issue: Empty Data Array
Cause: No records match the filters
Solution:
- Expand date range
- Remove or adjust filters
- Check if project has data for the selected period
Issue: Slow Report Generation
Cause: Large dataset or complex aggregations
Solution:
- Use pagination (
limitandskip) - Narrow date range
- Add more specific filters (project_id, region_id)
- Consider caching results
Best Practices
1. Always Handle Errors
try {
const result = await reportsService.generate(request);
setData(result.data);
} catch (error) {
console.error('Report generation failed:', error);
showErrorToast(error.message);
}
2. Use Pagination for Large Datasets
const request: ReportRequest = {
report_type: 'custom',
date_range: { start_date, end_date },
limit: 100, // Fetch 100 records at a time
skip: page * 100, // Offset for pagination
custom_config: { ... }
};
3. Cache Report Results
// Use React Query, SWR, or simple state management
const cacheKey = `report_${reportType}_${projectId}_${startDate}_${endDate}`;
const cached = localStorage.getItem(cacheKey);
if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
return JSON.parse(cached.data);
}
4. Show Loading States
{loading && <Spinner />}
{error && <ErrorMessage message={error} />}
{data && <ReportTable data={data} />}
5. Validate Dates Before Sending
const validateDateRange = (start: string, end: string) => {
const startDate = new Date(start);
const endDate = new Date(end);
if (startDate > endDate) {
throw new Error('Start date must be before end date');
}
if (endDate > new Date()) {
throw new Error('End date cannot be in the future');
}
};
Questions?
Contact the backend team or check:
- API Swagger docs:
/docs - Source code:
src/app/api/v1/reports.py - Service logic:
src/app/services/report_service.py - Feature docs:
docs/api/reports/CUSTOM_REPORTS_FEATURE.md