Events Dashboard — Frontend Integration Guide¶
Endpoints for the admin events panel. Provides event KPIs, per-event stats, promo code stats, ticket order stats, and XLSX export.
Prefix: /api/v1/admin/events-dashboard
Authentication: Bearer token (JWT) + user_type='admin'
Endpoints¶
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/events-dashboard/overview |
Consolidated event KPIs |
| GET | /admin/events-dashboard/events |
Per-event stats (paginated) |
| GET | /admin/events-dashboard/promo-codes |
Promo code stats |
| GET | /admin/events-dashboard/orders |
Ticket order stats |
| GET | /admin/events-dashboard/events/export |
Download XLSX report |
1. GET /admin/events-dashboard/overview¶
Consolidated KPIs for events, tickets, and revenue. Ready for dashboard cards.
Response (200 OK)¶
{
"total_events": 12,
"active_events": 5,
"upcoming_events": 3,
"past_events": 9,
"total_tickets_issued": 347,
"total_tickets_sold": 210,
"total_tickets_promo": 137,
"total_revenue": 10500.00,
"total_orders": 98,
"events_by_type": {
"movie_premiere": 8,
"concert": 4
}
}
Fields¶
| Field | Type | Description |
|---|---|---|
total_events |
int |
Total registered events |
active_events |
int |
Events with is_active=true |
upcoming_events |
int |
Events with event_date > now |
past_events |
int |
Events with event_date <= now |
total_tickets_issued |
int |
All tickets in the system |
total_tickets_sold |
int |
Tickets created via store purchase (ticket_order_id present) |
total_tickets_promo |
int |
Tickets created via promo code (promo_code_id present, no ticket_order_id) |
total_revenue |
float |
Revenue from confirmed orders (reais, e.g. 10500.00 = R$10.500,00) |
total_orders |
int |
Total confirmed ticket orders |
events_by_type |
dict[str, int] |
Count per event type key |
Flutter/Dart¶
class EventsOverview {
final int totalEvents;
final int activeEvents;
final int upcomingEvents;
final int pastEvents;
final int totalTicketsIssued;
final int totalTicketsSold;
final int totalTicketsPromo;
final double totalRevenue;
final int totalOrders;
final Map<String, int> eventsByType;
EventsOverview.fromJson(Map<String, dynamic> json)
: totalEvents = json['total_events'],
activeEvents = json['active_events'],
upcomingEvents = json['upcoming_events'],
pastEvents = json['past_events'],
totalTicketsIssued = json['total_tickets_issued'],
totalTicketsSold = json['total_tickets_sold'],
totalTicketsPromo = json['total_tickets_promo'],
totalRevenue = (json['total_revenue'] as num).toDouble(),
totalOrders = json['total_orders'],
eventsByType = Map<String, int>.from(json['events_by_type'] ?? {});
}
Future<EventsOverview> fetchOverview(String token) async {
final response = await dio.get(
'/api/v1/admin/events-dashboard/overview',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
return EventsOverview.fromJson(response.data);
}
2. GET /admin/events-dashboard/events¶
Per-event stats with ticket and order breakdowns. Paginated.
Query Params¶
| Param | Type | Default | Description |
|---|---|---|---|
page |
int |
0 |
Page offset (0-based) |
limit |
int |
20 |
Items per page (max 500) |
event_type |
string |
— | Filter by event type (e.g. movie_premiere) |
active_only |
bool |
false |
If true, only events with is_active=true |
Response (200 OK)¶
{
"total": 12,
"events": [
{
"event_id": "665a1b2c3d4e5f6a7b8c9d0e",
"title": "Pre-Estreia: O Retorno",
"event_type": "movie_premiere",
"event_date": "2026-04-15T19:00:00+00:00",
"location": "Cinema Central - Sala 3",
"capacity": 200,
"tickets_issued": 147,
"tickets_sold": 110,
"tickets_promo": 37,
"ticket_status": {
"available": 100,
"consumed": 40,
"expired": 7
},
"revenue": 5500.00,
"orders_count": 55,
"occupancy_pct": 73.5
}
]
}
Fields — event item¶
| Field | Type | Description |
|---|---|---|
event_id |
string |
MongoDB ObjectId |
title |
string |
Event title |
event_type |
string |
Event type key (e.g. movie_premiere) |
event_date |
string \| null |
ISO 8601 datetime |
location |
string |
Venue |
capacity |
int |
Max tickets (0 = unlimited/not set) |
tickets_issued |
int |
Real ticket count from tickets collection (via $lookup) |
tickets_sold |
int |
Tickets created via store purchase (ticket_order_id present) |
tickets_promo |
int |
Tickets created via promo code (promo_code_id present, no ticket_order_id) |
ticket_status.available |
int |
Tickets with status='available' |
ticket_status.consumed |
int |
Tickets with status='consumed' |
ticket_status.expired |
int |
Tickets with status='expired' |
revenue |
float |
Sum of paid orders (reais) |
orders_count |
int |
Count of paid orders |
occupancy_pct |
float |
tickets_issued / capacity * 100 (0.0 if no capacity) |
Note:
tickets_issuedhere is the live count from a$lookupjoin, not the denormalizedtickets_issuedcounter on the event document. These should match but the dashboard always shows the accurate value.
Flutter/Dart¶
Future<Map<String, dynamic>> fetchEventStats({
int page = 0,
int limit = 20,
String? eventType,
bool activeOnly = false,
}) async {
final response = await dio.get(
'/api/v1/admin/events-dashboard/events',
queryParameters: {
'page': page,
'limit': limit,
if (eventType != null) 'event_type': eventType,
if (activeOnly) 'active_only': true,
},
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
return response.data; // { total, events: [...] }
}
3. GET /admin/events-dashboard/promo-codes¶
Promo code stats for events.
Response (200 OK)¶
{
"total_promo_codes": 25,
"active_promo_codes": 18,
"total_tickets_from_promos": 210,
"by_type": {
"ticket": {
"total": 20,
"active": 15,
"tickets_generated": 190
},
"promo": {
"total": 5,
"active": 3,
"tickets_generated": 0
}
},
"top_codes": [
{
"code": "449GQW",
"code_type": "ticket",
"title": "Goiania VIP",
"tickets_generated": 45,
"is_active": true
}
]
}
Fields¶
| Field | Type | Description |
|---|---|---|
total_promo_codes |
int |
Total promo codes in the system |
active_promo_codes |
int |
Codes with is_active=true |
total_tickets_from_promos |
int |
All tickets created via promo codes (promo_code_id present, no ticket_order_id) |
by_type |
dict[str, object] |
Breakdown by code_type key |
by_type[key].total |
int |
Total codes of this type |
by_type[key].active |
int |
Active codes of this type |
by_type[key].tickets_generated |
int |
Tickets issued by this type |
top_codes |
list |
Top 10 codes by tickets generated |
top_codes[].code |
string |
Promo code string |
top_codes[].code_type |
string |
ticket or promo |
top_codes[].title |
string |
Promo code title |
top_codes[].tickets_generated |
int |
Tickets issued by this code |
top_codes[].is_active |
bool |
Whether code is currently active |
4. GET /admin/events-dashboard/orders¶
Ticket order stats.
Response (200 OK)¶
{
"total_orders": 156,
"total_revenue": 17350.00,
"by_status": {
"CONFIRMED": { "count": 110, "total_amount": 11000.00 },
"RECEIVED": { "count": 20, "total_amount": 2000.00 },
"PENDING": { "count": 8, "total_amount": 800.00 },
"succeeded": { "count": 18, "total_amount": 3550.00 }
},
"by_gateway": {
"asaas": { "count": 98, "total_amount": 9800.00 },
"stripe": { "count": 58, "total_amount": 7550.00 }
},
"by_payment_method": {
"credit_card": { "count": 89, "total_amount": 8900.00 },
"pix": { "count": 67, "total_amount": 8450.00 }
},
"avg_order_value": 111.22,
"avg_tickets_per_order": 1.5
}
Fields¶
| Field | Type | Description |
|---|---|---|
total_orders |
int |
Total ticket orders (all statuses) |
total_revenue |
float |
Sum of all order amounts (reais) |
by_status |
dict[str, object] |
Breakdown by gateway payment status (Asaas: CONFIRMED, RECEIVED, PENDING, OVERDUE; Stripe: succeeded, requires_action, canceled) |
by_gateway |
dict[str, object] |
Breakdown by payment gateway (asaas, stripe) |
by_payment_method |
dict[str, object] |
Breakdown by payment method (credit_card, pix, unknown for legacy orders) |
avg_order_value |
float |
Average order value in reais |
avg_tickets_per_order |
float |
Average tickets per order |
Each breakdown object (by_status, by_gateway, by_payment_method) has:
| Sub-field | Type | Description |
|---|---|---|
count |
int |
Number of orders in this group |
total_amount |
float |
Total amount for this group (reais) |
Flutter/Dart¶
class OrderStats {
final int totalOrders;
final double totalRevenue;
final Map<String, Map<String, dynamic>> byStatus;
final Map<String, Map<String, dynamic>> byGateway;
final Map<String, Map<String, dynamic>> byPaymentMethod;
final double avgOrderValue;
final double avgTicketsPerOrder;
OrderStats.fromJson(Map<String, dynamic> json)
: totalOrders = json['total_orders'],
totalRevenue = (json['total_revenue'] as num).toDouble(),
byStatus = Map<String, Map<String, dynamic>>.from(
(json['by_status'] as Map).map(
(k, v) => MapEntry(k as String, Map<String, dynamic>.from(v)))),
byGateway = Map<String, Map<String, dynamic>>.from(
(json['by_gateway'] as Map).map(
(k, v) => MapEntry(k as String, Map<String, dynamic>.from(v)))),
byPaymentMethod = Map<String, Map<String, dynamic>>.from(
(json['by_payment_method'] as Map).map(
(k, v) => MapEntry(k as String, Map<String, dynamic>.from(v)))),
avgOrderValue = (json['avg_order_value'] as num).toDouble(),
avgTicketsPerOrder = (json['avg_tickets_per_order'] as num).toDouble();
}
5. GET /admin/events-dashboard/events/export¶
Download all events with ticket and order stats as xlsx (default), csv (zip multi-aba) or json. Style follows ADR-059.
Query Params¶
| Param | Type | Default | Description |
|---|---|---|---|
format |
string |
xlsx |
xlsx (multi-sheet) / csv (zip) / json (nested) |
event_type |
string |
— | Filter by event type |
active_only |
bool |
false |
Only active events |
token |
string |
— | Bearer token (alternative to Authorization header — use for direct URL downloads) |
Response¶
format |
Content-Type | Filename |
|---|---|---|
xlsx |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
events_dashboard_YYYYMMDD_HHMM.xlsx |
csv |
application/zip |
events_dashboard_YYYYMMDD_HHMM.zip |
json |
application/json; charset=utf-8 |
events_dashboard_YYYYMMDD_HHMM.json |
The file contains two sheets:
Sheet 1 — Events
| Column | Description |
|---|---|
| ID | Event ObjectId |
| Title | Event title |
| Type | event_type key |
| Date | DD/MM/YYYY HH:MM |
| Location | Venue |
| Capacity | Max tickets |
| Tickets Issued | Live count |
| Available | status='available' count |
| Consumed | status='consumed' count |
| Expired | status='expired' count |
| Tickets Sold | Tickets from purchases |
| Tickets Promo | Tickets from promo codes |
| Revenue (R$) | Paid order revenue |
| Orders (paid) | Count of paid orders |
| Occupancy % | issued / capacity * 100 |
| Active | Yes / No |
| Box Office | Yes / No (is_sale_box_office) |
Sheet 2 — Tickets
| Column | Description |
|---|---|
| Ticket ID | Ticket ObjectId |
| Event | Event title |
| Event Date | DD/MM/YYYY HH:MM |
| Customer ID | Customer ObjectId |
| Status | available / consumed / expired |
| Promo Code | Code string (if from promo) |
| Order ID | TicketOrder ObjectId (if from purchase) |
| Created At | DD/MM/YYYY HH:MM |
| Consumed At | DD/MM/YYYY HH:MM (if consumed) |
Flutter/Dart — Download and save¶
Dois modos suportados:
Modo 1 — Dio com header (recomendado):
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
Future<File> downloadEventsDashboardXlsx({
required String token,
String? eventType,
bool activeOnly = false,
}) async {
final queryParams = <String, dynamic>{};
if (eventType != null) queryParams['event_type'] = eventType;
if (activeOnly) queryParams['active_only'] = true;
final response = await Dio().get(
'$baseUrl/api/v1/admin/events-dashboard/events/export',
queryParameters: queryParams,
options: Options(
headers: {'Authorization': 'Bearer $token'},
responseType: ResponseType.bytes,
),
);
final dir = await getApplicationDocumentsDirectory();
final filename = 'events_dashboard_${DateTime.now().millisecondsSinceEpoch}.xlsx';
final file = File('${dir.path}/$filename');
await file.writeAsBytes(response.data as List<int>);
return file;
}
Modo 2 — URL direta com token como query param (para launchUrl, WebView, share link):
import 'package:url_launcher/url_launcher.dart';
Future<void> openXlsxInBrowser(String token) async {
final uri = Uri.parse(
'$baseUrl/api/v1/admin/events-dashboard/events/export?token=$token',
);
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
Notes¶
- Revenue in reais (not cents). e.g.
17350.00= R$17.350,00. - Admin authentication required on all endpoints.
- Ticket origin discrimination: Tickets are classified by existing fields —
ticket_order_id(store purchase) andpromo_code_id(promo code). Tickets with neither are admin-created. - Order status:
by_statusshows gateway-native status values (CONFIRMED/RECEIVEDfor Asaas,succeededfor Stripe), not normalized lifecycle states. - Confirmed payment statuses:
CONFIRMED,RECEIVED(Asaas),succeeded(Stripe) — used for revenue and paid order counts. - Aggregation pipelines with
$lookup— may have higher latency on large datasets. events/exportis route-order sensitive — registered beforeevents/{event_id}in the router, so it is matched correctly.