nrb 4.0.3
nrb: ^4.0.3 copied to clipboard
A highly responsive Flutter table and report builder for complex nested headers, editable data grids, and premium Excel/PDF exports.
example/lib/main.dart
import 'dart:io' show Directory, File, Platform;
import 'dart:typed_data';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
// Ensure your package is correctly imported
import 'package:nrb/nrb.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
void main() {
runApp(
const MaterialApp(
debugShowCheckedModeBanner: false,
home: NRBPackageDemoScreen(),
),
);
}
/// This is a comprehensive showcase of the Nexora Report Builder (NRB) package.
///
/// It demonstrates how to integrate individual components (like Charts and Metrics)
/// alongside complex, interactive, and exportable Data Grids (NrbTableEngine).
class NRBPackageDemoScreen extends StatefulWidget {
const NRBPackageDemoScreen({super.key});
@override
State<NRBPackageDemoScreen> createState() => _NRBPackageDemoScreenState();
}
class _NRBPackageDemoScreenState extends State<NRBPackageDemoScreen> {
// --- BRAND COLORS ---
final Color _primaryBrandColor = const Color(0xFF0B7A3E);
final Color _secondaryBrandColor = const Color(0xFF1E88E5);
// ===========================================================================
// EXAMPLE 1: FINANCIAL REPORT DATA (Exportable Grid)
// ===========================================================================
/// [headers] define the column structure. Notice the use of `rowSpan` and
/// `colSpan` to create complex, grouped HTML-style table headers.
final List<List<NrbHeaderCell>> _financialHeaders = [
[
const NrbHeaderCell(
text: "Region",
colSpan: 1,
rowSpan: 2,
backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "Q4 Financials (USD)",
colSpan: 3,
backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "Operations",
colSpan: 2,
backgroundColor: Color(0xFF0B7A3E)),
],
[
const NrbHeaderCell(
text: "Target Rev.", backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "Actual Rev.", backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "Margin %", backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "Active Clients", backgroundColor: Color(0xFF0B7A3E)),
const NrbHeaderCell(
text: "SLA Uptime", backgroundColor: Color(0xFF0B7A3E)),
]
];
/// [tableData] contains the actual rows. Every element must be a `ReportCell`.
/// Here we use `TextCell` which supports automatic text formatting.
final List<List<ReportCell>> _financialData = [
[
const TextCell(
itemContent: "North America", textAlignment: Alignment.centerLeft),
const TextCell(itemContent: "1200000", isAmount: true), // Formats automatically
const TextCell(itemContent: "1350000", isAmount: true),
const TextCell(itemContent: "28%"),
const TextCell(itemContent: "145"),
const TextCell(itemContent: "99.99%"),
],
[
const TextCell(
itemContent: "Europe", textAlignment: Alignment.centerLeft),
const TextCell(itemContent: "950000", isAmount: true),
const TextCell(itemContent: "920000", isAmount: true),
const TextCell(itemContent: "24%"),
const TextCell(itemContent: "112"),
const TextCell(itemContent: "99.95%"),
],
[
const TextCell(
itemContent: "Global Ops",
textAlignment: Alignment.centerLeft,
isBold: true),
const TextCell(itemContent: "2150000", isAmount: true, isBold: true),
const TextCell(itemContent: "2270000", isAmount: true, isBold: true),
const TextCell(itemContent: "26%", isBold: true),
const TextCell(itemContent: "257", isBold: true),
const TextCell(itemContent: "99.97%", isBold: true),
],
];
// ===========================================================================
// EXAMPLE 2: EMPLOYEE REVIEW FORM (Interactive Grid)
// ===========================================================================
/// This grid demonstrates editable cells using `TextFieldCell`.
final List<List<NrbHeaderCell>> _employeeHeaders = [
[
const NrbHeaderCell(
text: "Employee",
colSpan: 1,
rowSpan: 2,
backgroundColor: Color(0xFF1E88E5)),
const NrbHeaderCell(
text: "Core Metrics",
colSpan: 2,
backgroundColor: Color(0xFF1E88E5)),
const NrbHeaderCell(
text: "Manager Review",
colSpan: 1,
backgroundColor: Color(0xFF1E88E5)),
],
[
const NrbHeaderCell(
text: "Tasks Completed", backgroundColor: Color(0xFF1E88E5)),
const NrbHeaderCell(
text: "Efficiency Score", backgroundColor: Color(0xFF1E88E5)),
const NrbHeaderCell(
text: "Bonus (Editable)", backgroundColor: Color(0xFF1E88E5)),
]
];
final List<List<ReportCell>> _employeeData = [
[
const TextCell(
itemContent: "Alice Smith", textAlignment: Alignment.centerLeft),
const TextCell(itemContent: "124"),
const TextCell(itemContent: "94%"),
// Interactive Cell where users can type numbers directly into the table
TextFieldCell(
initialValue: "\$5,000", keyboardType: TextInputType.number),
],
[
const TextCell(
itemContent: "Bob Jones", textAlignment: Alignment.centerLeft),
const TextCell(itemContent: "98"),
const TextCell(itemContent: "88%"),
TextFieldCell(
initialValue: "\$3,200", keyboardType: TextInputType.number),
],
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F7FA), // Light enterprise gray background
appBar: AppBar(
title: const Text(
'NRB Enterprise Dashboard',
style: TextStyle(fontWeight: FontWeight.bold),
),
backgroundColor: Colors.white,
foregroundColor: Colors.black87,
elevation: 1,
),
body: LayoutBuilder(
builder: (context, constraints) {
final bool isDesktop = constraints.maxWidth > 1000;
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// -------------------------------------------------------------
// SECTION 1: METRICS & KPI GAUGES
// Demonstrating NRB Metric Cards, Donut Charts, and Gauges
// -------------------------------------------------------------
const _SectionTitle("Key Performance Indicators (NRB Charts)"),
const SizedBox(height: 12),
Wrap(
spacing: 16.0,
runSpacing: 16.0,
children: [
_buildKpiCard("Project Completion", 75.0, _primaryBrandColor),
_buildGaugeCard("Server Uptime", 99.9),
const NrbMetricCard(
title: "Daily Tasks",
value: "142",
backgroundColor: Color(0xFF1976D2),
),
],
),
const SizedBox(height: 40),
// -------------------------------------------------------------
// SECTION 2: COMPLEX DATA GRIDS (NrbTableEngine)
// This responsive layout places charts next to tables on desktop,
// and stacks them vertically on mobile devices.
// -------------------------------------------------------------
if (isDesktop)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Left Column: Visual Analytics
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _SectionTitle("Analytics (NRB Graphs)"),
const SizedBox(height: 12),
_buildChartCard(
title: "Monthly Revenue",
child: const NrbBarChart(
data: [120, 150, 180, 220, 300, 280],
labels: ["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
height: 200,
width: double.infinity,
barColor: Color(0xFF0B7A3E),
),
),
],
),
),
const SizedBox(width: 24),
// Right Column: Interactive Tables
Expanded(
flex: 6,
child: _buildDataGridColumn(),
),
],
)
else
// Mobile Layout: Stack everything vertically
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _SectionTitle("Analytics (NRB Graphs)"),
const SizedBox(height: 12),
_buildChartCard(
title: "Monthly Revenue",
child: const NrbBarChart(
data: [120, 150, 180, 220, 300, 280],
labels: ["Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
height: 150,
width: double.infinity,
barColor: Color(0xFF0B7A3E),
),
),
const SizedBox(height: 40),
_buildDataGridColumn(),
],
),
const SizedBox(height: 40), // Bottom padding
],
),
);
},
),
);
}
/// Builds the vertical column containing both NrbTableEngine examples.
Widget _buildDataGridColumn() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// =====================================================================
// NRB TABLE ENGINE EXAMPLE 1: The Exportable Grid
// Features: Frozen first column, Excel/PDF exporting, custom branding.
// =====================================================================
const _SectionTitle("Regional Financials (NrbTableEngine - Exportable)"),
const SizedBox(height: 12),
_buildGridContainer(
height: 380, // Constrain the height so it scrolls internally
child: NrbTableEngine(
frozenColumnsCount: 1, // Locks the 'Region' column during horizontal scroll
headers: _financialHeaders,
tableData: _financialData,
primaryUiColor: _primaryBrandColor,
primaryUiTextColor: Colors.white,
// Enable the floating action button for downloading/sharing
enableDownload: true,
showDownloadFloatingButton: true,
packageName: "com.inl.testapp", // Required for export backend
apiKey: "fe2b22a6-fd19-4466-b2fa-ff7262a5993a", // Required for export backend
reportName: "Financial_Report",
onDownloadCompleted: _handleDownloadSuccess,
),
),
const SizedBox(height: 40),
// =====================================================================
// NRB TABLE ENGINE EXAMPLE 2: The Interactive Form
// Features: Text input fields directly inside the table structure.
// =====================================================================
const _SectionTitle("Employee Performance (NrbTableEngine - Editable)"),
const SizedBox(height: 12),
_buildGridContainer(
height: 300,
child: NrbTableEngine(
frozenColumnsCount: 1,
bodyCellHeight: 48, // Taller cells to comfortably fit TextFields
headers: _employeeHeaders,
tableData: _employeeData,
primaryUiColor: _secondaryBrandColor,
primaryUiTextColor: Colors.white,
// Disable downloads for this specific editable grid
enableDownload: false,
showDownloadFloatingButton: false,
),
),
],
);
}
// --- HELPER WIDGETS FOR UI STYLING ---
/// A common container style for wrapping charts and tables in a clean card.
Widget _buildGridContainer({required double height, required Widget child}) {
return Container(
height: height,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
clipBehavior: Clip.antiAlias, // Ensures the table respects the border radius
child: child,
);
}
/// Builds a simple card wrapper around an NRB Chart widget.
Widget _buildChartCard({required String title, required Widget child}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87),
),
const SizedBox(height: 24),
child,
],
),
);
}
/// Builds a Donut KPI Chart.
Widget _buildKpiCard(String title, double value, Color color) {
return Container(
width: 140,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
NrbDonutChart(
value: value,
size: 70,
progressColor: color,
trackColor: Colors.grey.shade200,
strokeWidth: 8,
centerContent: Text(
"${value.toStringAsFixed(1)}%",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.grey.shade800,
),
),
),
const SizedBox(height: 16),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black54),
),
],
),
);
}
/// Builds a Gauge KPI Chart.
Widget _buildGaugeCard(String title, double percentage) {
return Container(
width: 140,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
height: 70,
child: Center(
child: NrbGaugeChart(
value: percentage,
size: 100,
strokeWidth: 12.0,
showLabels: false,
segments: [
NrbGaugeSegment(startValue: 0, endValue: 50, color: const Color(0xFFF0716A)),
NrbGaugeSegment(startValue: 50, endValue: 75, color: const Color(0xFFFFCA3A)),
NrbGaugeSegment(startValue: 75, endValue: 100, color: const Color(0xFF67B28C)),
],
centerContent: Text(
"${percentage.toStringAsFixed(0)}%",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: Colors.grey.shade800,
),
),
),
),
),
const SizedBox(height: 16),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12, fontWeight: FontWeight.w600, color: Colors.black54),
),
],
),
);
}
// --- DEVICE NATIVE FILE DOWNLOADING ---
/// Native handler for saving the exported document to the user's device.
Future<void> _handleDownloadSuccess(Uint8List bytes, String fileName) async {
if (!kIsWeb) {
bool hasPermission = false;
// Handle specific Android SDK permissions
if (Platform.isAndroid) {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
if (androidInfo.version.sdkInt >= 33) {
hasPermission = true;
} else {
var status = await Permission.storage.status;
if (!status.isGranted) {
status = await Permission.storage.request();
}
hasPermission = status.isGranted;
}
} else {
hasPermission = true; // iOS/Desktop
}
// Save file if permissions are granted
if (hasPermission) {
try {
final directory = Directory('/storage/emulated/0/Download');
if (!await directory.exists()) {
await directory.create(recursive: true);
}
final file = File('${directory.path}/$fileName');
await file.writeAsBytes(bytes);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Report saved to: ${file.path}'),
backgroundColor: Colors.green,
behavior: SnackBarBehavior.floating,
),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Save failed: $e'),
backgroundColor: Colors.red),
);
}
}
}
}
}
}
/// A simple helper for consistent section headings.
class _SectionTitle extends StatelessWidget {
final String title;
const _SectionTitle(this.title);
@override
Widget build(BuildContext context) {
return Text(
title,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87),
);
}
}