Reports
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../providers/reports_provider.dart';
|
||||
import 'report_card_wrapper.dart';
|
||||
|
||||
/// Horizontal bar chart — top 10 offices by ticket count.
|
||||
/// Uses custom Flutter widgets so labels sit genuinely inside each bar.
|
||||
class TopOfficesTicketsChart extends ConsumerWidget {
|
||||
const TopOfficesTicketsChart({super.key, this.repaintKey});
|
||||
final GlobalKey? repaintKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asyncData = ref.watch(topOfficesTicketsReportProvider);
|
||||
return asyncData.when(
|
||||
loading: () => ReportCardWrapper(
|
||||
title: 'Top Offices by Tickets',
|
||||
isLoading: true,
|
||||
height: 320,
|
||||
repaintBoundaryKey: repaintKey,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
error: (e, _) => ReportCardWrapper(
|
||||
title: 'Top Offices by Tickets',
|
||||
error: e.toString(),
|
||||
repaintBoundaryKey: repaintKey,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
data: (data) => _HorizontalBarBody(
|
||||
data: data,
|
||||
title: 'Top Offices by Tickets',
|
||||
barColor: Theme.of(context).colorScheme.primary,
|
||||
repaintKey: repaintKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizontal bar chart — top 10 offices by task count.
|
||||
class TopOfficesTasksChart extends ConsumerWidget {
|
||||
const TopOfficesTasksChart({super.key, this.repaintKey});
|
||||
final GlobalKey? repaintKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asyncData = ref.watch(topOfficesTasksReportProvider);
|
||||
return asyncData.when(
|
||||
loading: () => ReportCardWrapper(
|
||||
title: 'Top Offices by Tasks',
|
||||
isLoading: true,
|
||||
height: 320,
|
||||
repaintBoundaryKey: repaintKey,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
error: (e, _) => ReportCardWrapper(
|
||||
title: 'Top Offices by Tasks',
|
||||
error: e.toString(),
|
||||
repaintBoundaryKey: repaintKey,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
data: (data) => _HorizontalBarBody(
|
||||
data: data,
|
||||
title: 'Top Offices by Tasks',
|
||||
barColor: Theme.of(context).colorScheme.secondary,
|
||||
repaintKey: repaintKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Shared horizontal-bar body (pure Flutter widgets) ───
|
||||
|
||||
class _HorizontalBarBody extends StatelessWidget {
|
||||
const _HorizontalBarBody({
|
||||
required this.data,
|
||||
required this.title,
|
||||
required this.barColor,
|
||||
this.repaintKey,
|
||||
});
|
||||
|
||||
final List<NamedCount> data;
|
||||
final String title;
|
||||
final Color barColor;
|
||||
final GlobalKey? repaintKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (data.isEmpty) {
|
||||
return ReportCardWrapper(
|
||||
title: title,
|
||||
repaintBoundaryKey: repaintKey,
|
||||
child: const SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: Text('No data for selected period')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final maxCount = data.fold<int>(0, (m, e) => e.count > m ? e.count : m);
|
||||
final height = (data.length * 34.0).clamp(160.0, 420.0);
|
||||
final onBarColor = barColor.computeLuminance() > 0.5
|
||||
? Colors.black87
|
||||
: Colors.white;
|
||||
|
||||
return ReportCardWrapper(
|
||||
title: title,
|
||||
repaintBoundaryKey: repaintKey,
|
||||
height: height,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final availableWidth = constraints.maxWidth;
|
||||
final labelStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: onBarColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: data.map((item) {
|
||||
final fraction = maxCount > 0 ? item.count / maxCount : 0.0;
|
||||
final barWidth = (fraction * availableWidth).clamp(
|
||||
120.0,
|
||||
availableWidth,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Tooltip(
|
||||
message: '${item.name}: ${item.count}',
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: barWidth,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: barColor,
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
right: Radius.circular(6),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name,
|
||||
style: labelStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text('${item.count}', style: labelStyle),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user