tasq/lib/screens/reports/widgets/top_subjects_charts.dart
2026-03-03 07:38:40 +08:00

178 lines
5.5 KiB
Dart

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 ticket subjects (pg_trgm clustered).
/// Uses custom Flutter widgets so labels sit genuinely inside each bar.
class TopTicketSubjectsChart extends ConsumerWidget {
const TopTicketSubjectsChart({super.key, this.repaintKey});
final GlobalKey? repaintKey;
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncData = ref.watch(topTicketSubjectsReportProvider);
return asyncData.when(
loading: () => ReportCardWrapper(
title: 'Top Ticket Subjects',
isLoading: true,
height: 320,
repaintBoundaryKey: repaintKey,
child: const SizedBox.shrink(),
),
error: (e, _) => ReportCardWrapper(
title: 'Top Ticket Subjects',
error: e.toString(),
repaintBoundaryKey: repaintKey,
child: const SizedBox.shrink(),
),
data: (data) => _SubjectBarBody(
data: data,
title: 'Top Ticket Subjects',
barColor: Theme.of(context).colorScheme.tertiary,
repaintKey: repaintKey,
),
);
}
}
/// Horizontal bar chart — top 10 task subjects (pg_trgm clustered).
class TopTaskSubjectsChart extends ConsumerWidget {
const TopTaskSubjectsChart({super.key, this.repaintKey});
final GlobalKey? repaintKey;
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncData = ref.watch(topTaskSubjectsReportProvider);
return asyncData.when(
loading: () => ReportCardWrapper(
title: 'Top Task Subjects',
isLoading: true,
height: 320,
repaintBoundaryKey: repaintKey,
child: const SizedBox.shrink(),
),
error: (e, _) => ReportCardWrapper(
title: 'Top Task Subjects',
error: e.toString(),
repaintBoundaryKey: repaintKey,
child: const SizedBox.shrink(),
),
data: (data) => _SubjectBarBody(
data: data,
title: 'Top Task Subjects',
barColor: Theme.of(context).colorScheme.secondary,
repaintKey: repaintKey,
),
);
}
}
// ─── Shared horizontal-bar body (pure Flutter widgets) ───
class _SubjectBarBody extends StatelessWidget {
const _SubjectBarBody({
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,
);
final label = _titleCase(item.name);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Tooltip(
message: '$label: ${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(
label,
style: labelStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 6),
Text('${item.count}', style: labelStyle),
],
),
),
),
),
);
}).toList(),
);
},
),
);
}
}
String _titleCase(String s) {
if (s.isEmpty) return s;
return s
.split(' ')
.map((w) {
if (w.isEmpty) return w;
return '${w[0].toUpperCase()}${w.substring(1)}';
})
.join(' ');
}