Merge branch 'github-action' of https://github.com/Oloodi/krow-workforce into github-action

This commit is contained in:
Achintha Isuru
2026-02-20 01:26:30 -05:00
8 changed files with 580 additions and 398 deletions

View File

@@ -1,4 +1,4 @@
library client_reports;
library;
export 'src/reports_module.dart';
export 'src/presentation/pages/reports_page.dart';

View File

@@ -1,14 +1,16 @@
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_event.dart';
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_state.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import '../widgets/reports_page/index.dart';
/// The main Reports page for the client application.
///
/// Displays key performance metrics and quick access to various reports.
/// Handles tab-based time period selection (Today, Week, Month, Quarter).
class ReportsPage extends StatefulWidget {
const ReportsPage({super.key});
@@ -49,7 +51,11 @@ class _ReportsPageState extends State<ReportsPage>
_tabController = TabController(length: 4, vsync: this);
_summaryBloc = Modular.get<ReportsSummaryBloc>();
_loadSummary(0);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_tabController.addListener(() {
if (!_tabController.indexIsChanging) {
_loadSummary(_tabController.index);
@@ -170,228 +176,13 @@ class _ReportsPageState extends State<ReportsPage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Key Metrics — driven by BLoC
BlocBuilder<ReportsSummaryBloc, ReportsSummaryState>(
builder: (context, state) {
if (state is ReportsSummaryLoading ||
state is ReportsSummaryInitial) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32),
child: Center(child: CircularProgressIndicator()),
);
}
if (state is ReportsSummaryError) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.tagError,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(UiIcons.warning,
color: UiColors.error, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
state.message,
style: const TextStyle(
color: UiColors.error, fontSize: 12),
),
),
],
),
),
);
}
final summary = (state as ReportsSummaryLoaded).summary;
final currencyFmt = NumberFormat.currency(
symbol: '\$', decimalDigits: 0);
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.2,
children: [
_MetricCard(
icon: UiIcons.clock,
label: context
.t.client_reports.metrics.total_hrs.label,
value: summary.totalHours >= 1000
? '${(summary.totalHours / 1000).toStringAsFixed(1)}k'
: summary.totalHours.toStringAsFixed(0),
badgeText: context
.t.client_reports.metrics.total_hrs.badge,
badgeColor: UiColors.tagRefunded,
badgeTextColor: UiColors.primary,
iconColor: UiColors.primary,
),
_MetricCard(
icon: UiIcons.trendingUp,
label: context
.t.client_reports.metrics.ot_hours.label,
value: summary.otHours.toStringAsFixed(0),
badgeText: context
.t.client_reports.metrics.ot_hours.badge,
badgeColor: UiColors.tagValue,
badgeTextColor: UiColors.textSecondary,
iconColor: UiColors.textWarning,
),
_MetricCard(
icon: UiIcons.dollar,
label: context
.t.client_reports.metrics.total_spend.label,
value: summary.totalSpend >= 1000
? '\$${(summary.totalSpend / 1000).toStringAsFixed(1)}k'
: currencyFmt.format(summary.totalSpend),
badgeText: context
.t.client_reports.metrics.total_spend.badge,
badgeColor: UiColors.tagSuccess,
badgeTextColor: UiColors.textSuccess,
iconColor: UiColors.success,
),
_MetricCard(
icon: UiIcons.trendingUp,
label: context
.t.client_reports.metrics.fill_rate.label,
value: '${summary.fillRate.toStringAsFixed(0)}%',
badgeText: context
.t.client_reports.metrics.fill_rate.badge,
badgeColor: UiColors.tagInProgress,
badgeTextColor: UiColors.textLink,
iconColor: UiColors.iconActive,
),
_MetricCard(
icon: UiIcons.clock,
label: context
.t.client_reports.metrics.avg_fill_time.label,
value:
'${summary.avgFillTimeHours.toStringAsFixed(1)} hrs',
badgeText: context
.t.client_reports.metrics.avg_fill_time.badge,
badgeColor: UiColors.tagInProgress,
badgeTextColor: UiColors.textLink,
iconColor: UiColors.iconActive,
),
_MetricCard(
icon: UiIcons.warning,
label: context
.t.client_reports.metrics.no_show_rate.label,
value:
'${summary.noShowRate.toStringAsFixed(1)}%',
badgeText: context
.t.client_reports.metrics.no_show_rate.badge,
badgeColor: summary.noShowRate < 5
? UiColors.tagSuccess
: UiColors.tagError,
badgeTextColor: summary.noShowRate < 5
? UiColors.textSuccess
: UiColors.error,
iconColor: UiColors.destructive,
),
],
);
},
),
// Key Metrics Grid
const MetricsGrid(),
const SizedBox(height: 24),
// Quick Reports
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.t.client_reports.quick_reports.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
),
/*
TextButton.icon(
onPressed: () {},
icon: const Icon(UiIcons.download, size: 16),
label: Text(
context.t.client_reports.quick_reports.export_all),
style: TextButton.styleFrom(
foregroundColor: UiColors.textLink,
padding: EdgeInsets.zero,
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
*/
],
),
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.3,
children: [
_ReportCard(
icon: UiIcons.calendar,
name: context
.t.client_reports.quick_reports.cards.daily_ops,
iconBgColor: UiColors.tagInProgress,
iconColor: UiColors.primary,
route: './daily-ops',
),
_ReportCard(
icon: UiIcons.dollar,
name: context
.t.client_reports.quick_reports.cards.spend,
iconBgColor: UiColors.tagSuccess,
iconColor: UiColors.success,
route: './spend',
),
_ReportCard(
icon: UiIcons.users,
name: context
.t.client_reports.quick_reports.cards.coverage,
iconBgColor: UiColors.tagInProgress,
iconColor: UiColors.primary,
route: './coverage',
),
_ReportCard(
icon: UiIcons.warning,
name: context
.t.client_reports.quick_reports.cards.no_show,
iconBgColor: UiColors.tagError,
iconColor: UiColors.destructive,
route: './no-show',
),
_ReportCard(
icon: UiIcons.trendingUp,
name: context
.t.client_reports.quick_reports.cards.forecast,
iconBgColor: UiColors.tagPending,
iconColor: UiColors.textWarning,
route: './forecast',
),
_ReportCard(
icon: UiIcons.chart,
name: context
.t.client_reports.quick_reports.cards.performance,
iconBgColor: UiColors.tagInProgress,
iconColor: UiColors.primary,
route: './performance',
),
],
),
const SizedBox(height: 24),
// Quick Reports Section
const QuickReportsSection(),
const SizedBox(height: 40),
],
@@ -404,177 +195,3 @@ class _ReportsPageState extends State<ReportsPage>
);
}
}
class _MetricCard extends StatelessWidget {
final IconData icon;
final String label;
final String value;
final String badgeText;
final Color badgeColor;
final Color badgeTextColor;
final Color iconColor;
const _MetricCard({
required this.icon,
required this.label,
required this.value,
required this.badgeText,
required this.badgeColor,
required this.badgeTextColor,
required this.iconColor,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: UiColors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 12,
color: UiColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(10),
),
child: Text(
badgeText,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: badgeTextColor,
),
),
),
],
),
],
),
);
}
}
class _ReportCard extends StatelessWidget {
final IconData icon;
final String name;
final Color iconBgColor;
final Color iconColor;
final String route;
const _ReportCard({
required this.icon,
required this.name,
required this.iconBgColor,
required this.iconColor,
required this.route,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Modular.to.pushNamed(route),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: UiColors.black.withOpacity(0.02),
blurRadius: 2,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, size: 20, color: iconColor),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(
UiIcons.download,
size: 12,
color: UiColors.textSecondary,
),
const SizedBox(width: 4),
Text(
context.t.client_reports.quick_reports.two_click_export,
style: const TextStyle(
fontSize: 12,
color: UiColors.textSecondary,
),
),
],
),
],
),
],
),
),
);
}
}

View File

@@ -0,0 +1,5 @@
export 'metric_card.dart';
export 'metrics_grid.dart';
export 'quick_reports_section.dart';
export 'report_card.dart';
export 'reports_header.dart';

View File

@@ -0,0 +1,112 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A metric card widget for displaying key performance indicators.
///
/// Shows a metric with an icon, label, value, and a badge with contextual
/// information. Used in the metrics grid of the reports page.
class MetricCard extends StatelessWidget {
/// The icon to display for this metric.
final IconData icon;
/// The label describing the metric.
final String label;
/// The main value to display (e.g., "1.2k", "$50,000").
final String value;
/// Text to display in the badge.
final String badgeText;
/// Background color for the badge.
final Color badgeColor;
/// Text color for the badge.
final Color badgeTextColor;
/// Color for the icon.
final Color iconColor;
const MetricCard({
super.key,
required this.icon,
required this.label,
required this.value,
required this.badgeText,
required this.badgeColor,
required this.badgeTextColor,
required this.iconColor,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: UiColors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Icon and Label
Row(
children: [
Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 12,
color: UiColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
// Value and Badge
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(10),
),
child: Text(
badgeText,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: badgeTextColor,
),
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,152 @@
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_state.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'metric_card.dart';
/// A grid of key metrics driven by the ReportsSummaryBloc.
///
/// Displays 6 metrics in a 2-column grid:
/// - Total Hours
/// - OT Hours
/// - Total Spend
/// - Fill Rate
/// - Average Fill Time
/// - No-Show Rate
///
/// Handles loading, error, and success states.
class MetricsGrid extends StatelessWidget {
const MetricsGrid({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<ReportsSummaryBloc, ReportsSummaryState>(
builder: (context, state) {
// Loading or Initial State
if (state is ReportsSummaryLoading || state is ReportsSummaryInitial) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32),
child: Center(child: CircularProgressIndicator()),
);
}
// Error State
if (state is ReportsSummaryError) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.tagError,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(UiIcons.warning,
color: UiColors.error, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
state.message,
style: const TextStyle(
color: UiColors.error, fontSize: 12),
),
),
],
),
),
);
}
// Loaded State
final summary = (state as ReportsSummaryLoaded).summary;
final currencyFmt = NumberFormat.currency(
symbol: '\$', decimalDigits: 0);
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.2,
children: [
// Total Hours
MetricCard(
icon: UiIcons.clock,
label: context.t.client_reports.metrics.total_hrs.label,
value: summary.totalHours >= 1000
? '${(summary.totalHours / 1000).toStringAsFixed(1)}k'
: summary.totalHours.toStringAsFixed(0),
badgeText: context.t.client_reports.metrics.total_hrs.badge,
badgeColor: UiColors.tagRefunded,
badgeTextColor: UiColors.primary,
iconColor: UiColors.primary,
),
// OT Hours
MetricCard(
icon: UiIcons.trendingUp,
label: context.t.client_reports.metrics.ot_hours.label,
value: summary.otHours.toStringAsFixed(0),
badgeText: context.t.client_reports.metrics.ot_hours.badge,
badgeColor: UiColors.tagValue,
badgeTextColor: UiColors.textSecondary,
iconColor: UiColors.textWarning,
),
// Total Spend
MetricCard(
icon: UiIcons.dollar,
label: context.t.client_reports.metrics.total_spend.label,
value: summary.totalSpend >= 1000
? '\$${(summary.totalSpend / 1000).toStringAsFixed(1)}k'
: currencyFmt.format(summary.totalSpend),
badgeText: context.t.client_reports.metrics.total_spend.badge,
badgeColor: UiColors.tagSuccess,
badgeTextColor: UiColors.textSuccess,
iconColor: UiColors.success,
),
// Fill Rate
MetricCard(
icon: UiIcons.trendingUp,
label: context.t.client_reports.metrics.fill_rate.label,
value: '${summary.fillRate.toStringAsFixed(0)}%',
badgeText: context.t.client_reports.metrics.fill_rate.badge,
badgeColor: UiColors.tagInProgress,
badgeTextColor: UiColors.textLink,
iconColor: UiColors.iconActive,
),
// Average Fill Time
MetricCard(
icon: UiIcons.clock,
label: context.t.client_reports.metrics.avg_fill_time.label,
value: '${summary.avgFillTimeHours.toStringAsFixed(1)} hrs',
badgeText:
context.t.client_reports.metrics.avg_fill_time.badge,
badgeColor: UiColors.tagInProgress,
badgeTextColor: UiColors.textLink,
iconColor: UiColors.iconActive,
),
// No-Show Rate
MetricCard(
icon: UiIcons.warning,
label: context.t.client_reports.metrics.no_show_rate.label,
value: '${summary.noShowRate.toStringAsFixed(1)}%',
badgeText: context.t.client_reports.metrics.no_show_rate.badge,
badgeColor: summary.noShowRate < 5
? UiColors.tagSuccess
: UiColors.tagError,
badgeTextColor: summary.noShowRate < 5
? UiColors.textSuccess
: UiColors.error,
iconColor: UiColors.destructive,
),
],
);
},
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'report_card.dart';
/// A section displaying quick access report cards.
///
/// Shows 4 quick report cards for:
/// - Daily Operations
/// - Spend Analysis
/// - No-Show Rates
/// - Performance Reports
class QuickReportsSection extends StatelessWidget {
const QuickReportsSection({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title
Text(
context.t.client_reports.quick_reports.title,
style: UiTypography.headline2m.textPrimary,
),
// Quick Reports Grid
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.3,
children: [
// Daily Operations
ReportCard(
icon: UiIcons.calendar,
name: context.t.client_reports.quick_reports.cards.daily_ops,
iconBgColor: UiColors.tagInProgress,
iconColor: UiColors.primary,
route: './daily-ops',
),
// Spend Analysis
ReportCard(
icon: UiIcons.dollar,
name: context.t.client_reports.quick_reports.cards.spend,
iconBgColor: UiColors.tagSuccess,
iconColor: UiColors.success,
route: './spend',
),
// No-Show Rates
ReportCard(
icon: UiIcons.warning,
name: context.t.client_reports.quick_reports.cards.no_show,
iconBgColor: UiColors.tagError,
iconColor: UiColors.destructive,
route: './no-show',
),
// Performance Reports
ReportCard(
icon: UiIcons.chart,
name:
context.t.client_reports.quick_reports.cards.performance,
iconBgColor: UiColors.tagInProgress,
iconColor: UiColors.primary,
route: './performance',
),
],
),
],
);
}
}

View File

@@ -0,0 +1,105 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
/// A quick report card widget for navigating to specific reports.
///
/// Displays an icon, name, and a quick navigation to a report page.
/// Used in the quick reports grid of the reports page.
class ReportCard extends StatelessWidget {
/// The icon to display for this report.
final IconData icon;
/// The name/title of the report.
final String name;
/// Background color for the icon container.
final Color iconBgColor;
/// Color for the icon.
final Color iconColor;
/// Navigation route to the report page.
final String route;
const ReportCard({
super.key,
required this.icon,
required this.name,
required this.iconBgColor,
required this.iconColor,
required this.route,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => Modular.to.pushNamed(route),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: UiColors.black.withOpacity(0.02),
blurRadius: 2,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Icon Container
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: iconBgColor,
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, size: 20, color: iconColor),
),
// Name and Export Info
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(
UiIcons.download,
size: 12,
color: UiColors.textSecondary,
),
const SizedBox(width: 4),
Text(
context.t.client_reports.quick_reports
.two_click_export,
style: const TextStyle(
fontSize: 12,
color: UiColors.textSecondary,
),
),
],
),
],
),
],
),
),
);
}
}

View File

@@ -0,0 +1,116 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
/// Header widget for the Reports page.
///
/// Displays the title, back button, and tab selector for different time periods
/// (Today, Week, Month, Quarter).
class ReportsHeader extends StatelessWidget {
const ReportsHeader({
super.key,
required this.onTabChanged,
required this.tabController,
});
/// Called when a tab is selected.
final Function(int) onTabChanged;
/// The current tab controller for managing tab state.
final TabController tabController;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(
top: 60,
left: 20,
right: 20,
bottom: 32,
),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
UiColors.primary,
UiColors.buttonPrimaryHover,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
// Title and Back Button
Row(
children: [
GestureDetector(
onTap: () => Modular.to.toClientHome(),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: UiColors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(
UiIcons.arrowLeft,
color: UiColors.white,
size: 20,
),
),
),
const SizedBox(width: 12),
Text(
context.t.client_reports.title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: UiColors.white,
),
),
],
),
const SizedBox(height: 24),
// Tab Bar
_buildTabBar(context),
],
),
);
}
/// Builds the styled tab bar for time period selection.
Widget _buildTabBar(BuildContext context) {
return Container(
height: 44,
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: UiColors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: TabBar(
controller: tabController,
onTap: onTabChanged,
indicator: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(8),
),
labelColor: UiColors.primary,
unselectedLabelColor: UiColors.white,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent,
tabs: [
Tab(text: context.t.client_reports.tabs.today),
Tab(text: context.t.client_reports.tabs.week),
Tab(text: context.t.client_reports.tabs.month),
Tab(text: context.t.client_reports.tabs.quarter),
],
),
);
}
}