feat: Implement CoverageWidget for daily coverage metrics on the client home page and add .gitignore.
This commit is contained in:
19
apps/.gitignore
vendored
Normal file
19
apps/.gitignore
vendored
Normal 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/
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user