feat: Implement CoverageWidget for daily coverage metrics on the client home page and add .gitignore.

This commit is contained in:
Achintha Isuru
2026-01-21 16:08:44 -05:00
parent 4a67b2f541
commit eb10254757
3 changed files with 173 additions and 0 deletions

19
apps/.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# Old apps prototypes
prototypes/*
# AI prompts
ai_prompts/*
# Template feature
packages/features/shared/template_feature/*
# Generated files
*.g.dart
*.freezed.dart
# Dart/Flutter
.dart_tool/
.packages
.pub-cache/
.pub/
build/

View File

@@ -8,6 +8,7 @@ import '../blocs/client_home_bloc.dart';
import '../blocs/client_home_event.dart';
import '../blocs/client_home_state.dart';
import '../widgets/actions_widget.dart';
import '../widgets/coverage_widget.dart';
import '../widgets/live_activity_widget.dart';
import '../widgets/reorder_widget.dart';
import '../widgets/shift_order_form_sheet.dart';
@@ -341,6 +342,7 @@ class _ClientHomePageState extends State<ClientHomePage> {
next7DaysScheduled: state.next7DaysScheduled,
);
case 'coverage':
return const CoverageWidget();
case 'liveActivity':
return LiveActivityWidget(onViewAllPressed: () {});
default:

View File

@@ -0,0 +1,152 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A widget that displays the daily coverage metrics.
class CoverageWidget extends StatelessWidget {
/// The total number of shifts needed.
final int totalNeeded;
/// The number of confirmed shifts.
final int totalConfirmed;
/// The percentage of coverage (0-100).
final int coveragePercent;
/// Creates a [CoverageWidget].
const CoverageWidget({
super.key,
this.totalNeeded = 10,
this.totalConfirmed = 8,
this.coveragePercent = 80,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"TODAY'S COVERAGE",
style: UiTypography.footnote1b.copyWith(
color: UiColors.textPrimary,
letterSpacing: 0.5,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical:
2, // 2px is not in metrics, using hardcoded for small tweaks or space0/space1
),
decoration: BoxDecoration(
color: UiColors.tagActive,
borderRadius: UiConstants.radiusLg,
),
child: Text(
'$coveragePercent% Covered',
style: UiTypography.footnote2b.copyWith(
color: UiColors.textSuccess,
),
),
),
],
),
const SizedBox(height: UiConstants.space2),
Row(
children: [
Expanded(
child: _MetricCard(
icon: UiIcons.target,
iconColor: UiColors.primary,
label: 'Needed',
value: '$totalNeeded',
),
),
const SizedBox(width: UiConstants.space2),
Expanded(
child: _MetricCard(
icon: UiIcons.success,
iconColor: UiColors.iconSuccess,
label: 'Filled',
value: '$totalConfirmed',
valueColor: UiColors.textSuccess,
),
),
const SizedBox(width: UiConstants.space2),
Expanded(
child: _MetricCard(
icon: UiIcons.error,
iconColor: UiColors.iconError,
label: 'Open',
value: '${totalNeeded - totalConfirmed}',
valueColor: UiColors.textError,
),
),
],
),
],
);
}
}
class _MetricCard extends StatelessWidget {
final IconData icon;
final Color iconColor;
final String label;
final String value;
final Color? valueColor;
const _MetricCard({
required this.icon,
required this.iconColor,
required this.label,
required this.value,
this.valueColor,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(UiConstants.space2 + 2), // 10px
decoration: BoxDecoration(
color: UiColors.cardViewBackground,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: [
BoxShadow(
color: UiColors.black.withValues(alpha: 0.02),
blurRadius: 2,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, size: 14, color: iconColor),
const SizedBox(width: 6), // 6px
Text(
label,
style: UiTypography.footnote2m.copyWith(
color: UiColors.textSecondary,
),
),
],
),
const SizedBox(width: 6), // 6px
Text(
value,
style: UiTypography.headline3m.copyWith(
color: valueColor ?? UiColors.textPrimary,
fontWeight:
FontWeight.bold, // header3 is usually bold, but ensuring
),
),
],
),
);
}
}