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