InventoryAgent/inventory_monitor_app/lib/dashboard_screen.dart
2025-10-20 00:03:49 +08:00

270 lines
8.9 KiB
Dart

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:inventory_monitor_app/models/dashboard_models.dart';
import 'package:inventory_monitor_app/services/api_service.dart';
import 'package:inventory_monitor_app/widgets/chart_card.dart';
import 'package:inventory_monitor_app/widgets/kpi_card.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final ApiService _apiService = ApiService();
late Future<DashboardSummary> _summaryFuture;
late Future<List<ChartDataPoint>> _osDistributionFuture;
late Future<Map<String, double>> _storageFuture;
late Future<List<ChartDataPoint>> _adminsFuture;
@override
void initState() {
super.initState();
_summaryFuture = _apiService.getDashboardSummary();
_osDistributionFuture = _apiService.getOsDistribution();
_storageFuture = _apiService.getStorageByType();
_adminsFuture = _apiService.getTopAdminAccounts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildKpiSection(),
const SizedBox(height: 24),
_buildChartSection(),
],
),
),
);
}
Widget _buildKpiSection() {
return FutureBuilder<DashboardSummary>(
future: _summaryFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
if (!snapshot.hasData) {
return const Center(child: Text('No summary data available.'));
}
final summary = snapshot.data!;
return Wrap(
spacing: 16,
runSpacing: 16,
children: [
KpiCard(
title: 'Total Devices',
value: summary.totalDevices.toString(),
icon: Icons.devices),
KpiCard(
title: 'Laptops',
value: summary.totalLaptops.toString(),
icon: Icons.laptop),
KpiCard(
title: 'Desktops',
value: summary.totalDesktops.toString(),
icon: Icons.desktop_windows),
KpiCard(
title: 'Servers',
value: summary.totalServers.toString(),
icon: Icons.dns),
KpiCard(
title: 'Printers w/ SN',
value: summary.printersWithSerial.toString(),
icon: Icons.print),
KpiCard(
title: 'Drives Failing',
value: summary.drivesFailing.toString(),
icon: Icons.error_outline,
color: Colors.red.shade700),
],
);
},
);
}
Widget _buildChartSection() {
return LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = constraints.maxWidth > 1200 ? 2 : 1;
return GridView.count(
crossAxisCount: crossAxisCount,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.8,
children: [
_buildDeviceTypeChart(),
_buildOsDistributionChart(),
_buildStorageChart(),
_buildAdminAccountsChart(),
],
);
},
);
}
Widget _buildDeviceTypeChart() {
return FutureBuilder<DashboardSummary>(
future: _summaryFuture,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const ChartCard(
title: 'Devices by Type',
child: Center(child: CircularProgressIndicator()));
final summary = snapshot.data!;
return ChartCard(
title: 'Devices by Type',
child: PieChart(
PieChartData(
sections: [
PieChartSectionData(
value: summary.totalLaptops.toDouble(),
title: 'Laptops',
color: Colors.blue,
radius: 50),
PieChartSectionData(
value: summary.totalDesktops.toDouble(),
title: 'Desktops',
color: Colors.green,
radius: 50),
PieChartSectionData(
value: summary.totalServers.toDouble(),
title: 'Servers',
color: Colors.orange,
radius: 50),
],
),
),
);
},
);
}
Widget _buildOsDistributionChart() {
return FutureBuilder<List<ChartDataPoint>>(
future: _osDistributionFuture,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const ChartCard(
title: 'OS Distribution',
child: Center(child: CircularProgressIndicator()));
final data = snapshot.data!;
return ChartCard(
title: 'OS Distribution',
child: BarChart(
BarChartData(
barGroups: data.asMap().entries.map((entry) {
return BarChartGroupData(x: entry.key, barRods: [
BarChartRodData(
toY: entry.value.count.toDouble(), color: Colors.purple)
]);
}).toList(),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) => Text(
data[value.toInt()].label.split(' ').last,
style: const TextStyle(fontSize: 10)),
reservedSize: 40)),
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: true, reservedSize: 40)),
topTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles:
const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
),
),
);
},
);
}
Widget _buildStorageChart() {
return FutureBuilder<Map<String, double>>(
future: _storageFuture,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const ChartCard(
title: 'Total Storage (TB)',
child: Center(child: CircularProgressIndicator()));
final data = snapshot.data!;
return ChartCard(
title: 'Total Storage (TB)',
child: BarChart(BarChartData(
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(
toY: data['ssd']!, color: Colors.cyan, width: 40)
], showingTooltipIndicators: [
0
]),
BarChartGroupData(x: 1, barRods: [
BarChartRodData(
toY: data['hdd']!, color: Colors.amber, width: 40)
], showingTooltipIndicators: [
0
]),
],
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) =>
Text(value == 0 ? 'SSD' : 'HDD')))),
)),
);
},
);
}
Widget _buildAdminAccountsChart() {
return FutureBuilder<List<ChartDataPoint>>(
future: _adminsFuture,
builder: (context, snapshot) {
if (!snapshot.hasData)
return const ChartCard(
title: 'Top 5 Local Admins',
child: Center(child: CircularProgressIndicator()));
final data = snapshot.data!;
return ChartCard(
title: 'Top 5 Local Admins',
child: BarChart(BarChartData(
barGroups: data
.asMap()
.entries
.map((e) => BarChartGroupData(x: e.key, barRods: [
BarChartRodData(
toY: e.value.count.toDouble(), color: Colors.teal)
]))
.toList(),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) => Text(
data[value.toInt()].label,
style: const TextStyle(fontSize: 10)),
reservedSize: 40))),
)),
);
},
);
}
}