270 lines
8.9 KiB
Dart
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))),
|
|
)),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|