a7fd1e74f1
Add StatTiles and BadgesStrip widgets wired into ProgrammerDashboard. StatTiles shows 2 tiles (focus + completed) when showSheetStats is false, and 5 tiles (+ streak, first-pass approval, help given) when the viewing user's role is programmer. BadgesStrip renders earned/locked chips in a horizontal Wrap with tap-to-dialog detail. Dashboard feeds todayFocus via buildDayRows/totalSeconds, leaveDays via approvedLeaveDays, and sheets: const [] until Task 20 wires real data. Co-Authored-By: claude-flow <ruv@ruv.net>
149 lines
4.2 KiB
Dart
149 lines
4.2 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../../../utils/programmer_stats.dart';
|
|
import '../../../utils/programmer_task_ledger.dart';
|
|
import '../../../widgets/app_metric_card.dart';
|
|
|
|
/// A vertical list of KPI stat tiles for the programmer dashboard.
|
|
///
|
|
/// When [showSheetStats] is false only the first two tiles (today's focus and
|
|
/// completed-this-week) are shown; sheet-based tiles (streak, first-pass
|
|
/// approval, help given) are hidden and badges are computed without sheet data.
|
|
class StatTiles extends StatelessWidget {
|
|
const StatTiles({
|
|
super.key,
|
|
required this.stats,
|
|
required this.showSheetStats,
|
|
});
|
|
|
|
final ProgrammerStats stats;
|
|
final bool showSheetStats;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
final tiles = <Widget>[
|
|
_buildFocusTile(context, cs),
|
|
const SizedBox(height: 8),
|
|
_buildCompletedTile(context),
|
|
];
|
|
if (showSheetStats) {
|
|
tiles.addAll([
|
|
const SizedBox(height: 8),
|
|
_buildStreakTile(context),
|
|
const SizedBox(height: 8),
|
|
_buildFirstPassTile(context),
|
|
const SizedBox(height: 8),
|
|
_buildHelpGivenTile(context),
|
|
]);
|
|
}
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: tiles,
|
|
);
|
|
}
|
|
|
|
Widget _buildFocusTile(BuildContext context, ColorScheme cs) {
|
|
final progressValue =
|
|
math.min(1.0, stats.todayFocus.inSeconds / (8 * 3600));
|
|
final trailing = Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
SizedBox(
|
|
width: 40,
|
|
height: 40,
|
|
child: CircularProgressIndicator(
|
|
value: progressValue,
|
|
strokeWidth: 6,
|
|
backgroundColor: cs.surfaceContainerHighest,
|
|
color: cs.primary,
|
|
strokeCap: StrokeCap.round,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'/ 8h',
|
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
color: cs.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
return AppMetricCard(
|
|
title: "TODAY'S FOCUS",
|
|
value: formatDurationHm(stats.todayFocus),
|
|
trailing: trailing,
|
|
);
|
|
}
|
|
|
|
Widget _buildCompletedTile(BuildContext context) {
|
|
final diff = stats.completedThisWeek - stats.completedLastWeek;
|
|
final String trailingText;
|
|
if (diff > 0) {
|
|
trailingText = '↑$diff vs last week';
|
|
} else if (diff < 0) {
|
|
trailingText = '↓${-diff} vs last week';
|
|
} else {
|
|
trailingText = 'Same as last week';
|
|
}
|
|
return AppMetricCard(
|
|
title: 'COMPLETED THIS WEEK',
|
|
value: stats.completedThisWeek.toString(),
|
|
trailing: Text(
|
|
trailingText,
|
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStreakTile(BuildContext context) {
|
|
final n = stats.currentStreak;
|
|
final dayLabel = n == 1 ? '1 day' : '$n days';
|
|
return AppMetricCard(
|
|
title: 'STREAK',
|
|
value: dayLabel,
|
|
icon: Icons.local_fire_department_outlined,
|
|
trailing: Text(
|
|
'Best ${stats.bestStreak}',
|
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFirstPassTile(BuildContext context) {
|
|
final value = stats.firstPassRate != null
|
|
? '${(stats.firstPassRate! * 100).round()}%'
|
|
: '—';
|
|
return AppMetricCard(
|
|
title: 'FIRST-PASS APPROVAL',
|
|
value: value,
|
|
trailing: Text(
|
|
'Last 30 days',
|
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHelpGivenTile(BuildContext context) {
|
|
return AppMetricCard(
|
|
title: 'HELP GIVEN',
|
|
value: formatDurationHm(stats.helpGivenThisMonth),
|
|
trailing: Text(
|
|
'This month',
|
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|