feat: Enhance benefits section and layout for improved user experience

This commit is contained in:
Achintha Isuru
2026-03-03 20:47:15 -05:00
parent a7d66a1efe
commit 4474a732c2
8 changed files with 47 additions and 70 deletions

View File

@@ -1,6 +1,8 @@
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
import 'package:staff_home/src/presentation/widgets/home_page/section_layout.dart'; import 'package:staff_home/src/presentation/widgets/home_page/section_layout.dart';
@@ -28,6 +30,8 @@ class BenefitsSection extends StatelessWidget {
return SectionLayout( return SectionLayout(
title: i18n.title, title: i18n.title,
action: i18n.view_all,
onAction: () => Modular.to.toBenefits(),
child: BenefitsWidget(benefits: state.benefits), child: BenefitsWidget(benefits: state.benefits),
); );
}, },

View File

@@ -17,7 +17,7 @@ class FullWidthDivider extends StatelessWidget {
children: [ children: [
const SizedBox(height: UiConstants.space10), const SizedBox(height: UiConstants.space10),
Transform.translate( Transform.translate(
offset: const Offset(UiConstants.space4, 0), offset: const Offset(-UiConstants.space4, 0),
child: SizedBox(width: screenWidth, child: const Divider()), child: SizedBox(width: screenWidth, child: const Divider()),
), ),
const SizedBox(height: UiConstants.space10), const SizedBox(height: UiConstants.space10),

View File

@@ -13,13 +13,14 @@ class RecommendedShiftCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final recI18n = t.staff.home.recommended_card; final recI18n = t.staff.home.recommended_card;
final size = MediaQuery.sizeOf(context);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Modular.to.toShiftDetails(shift); Modular.to.toShiftDetails(shift);
}, },
child: Container( child: Container(
width: 300, width: size.width * 0.8,
padding: const EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
@@ -28,11 +29,11 @@ class RecommendedShiftCard extends StatelessWidget {
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Container( Container(
width: UiConstants.space10, width: UiConstants.space10,
@@ -50,7 +51,7 @@ class RecommendedShiftCard extends StatelessWidget {
const SizedBox(width: UiConstants.space3), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@@ -21,26 +21,23 @@ class RecommendedShiftsSection extends StatelessWidget {
final t = Translations.of(context); final t = Translations.of(context);
final sectionsI18n = t.staff.home.sections; final sectionsI18n = t.staff.home.sections;
final emptyI18n = t.staff.home.empty_states; final emptyI18n = t.staff.home.empty_states;
final size = MediaQuery.sizeOf(context);
return SectionLayout( return SectionLayout(
title: sectionsI18n.recommended_for_you, title: sectionsI18n.recommended_for_you,
child: BlocBuilder<HomeCubit, HomeState>( child: BlocBuilder<HomeCubit, HomeState>(
builder: (context, state) { builder: (context, state) {
if (state.recommendedShifts.isEmpty) { if (state.recommendedShifts.isEmpty) {
return EmptyStateWidget( return EmptyStateWidget(message: emptyI18n.no_recommended_shifts);
message: emptyI18n.no_recommended_shifts,
);
} }
return SizedBox( return SizedBox(
height: 160, height: size.height * 0.15,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: state.recommendedShifts.length, itemCount: state.recommendedShifts.length,
clipBehavior: Clip.none, clipBehavior: Clip.none,
itemBuilder: (context, index) => Padding( itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(right: UiConstants.space3),
right: UiConstants.space3,
),
child: RecommendedShiftCard( child: RecommendedShiftCard(
shift: state.recommendedShifts[index], shift: state.recommendedShifts[index],
), ),

View File

@@ -39,11 +39,14 @@ class SectionHeader extends StatelessWidget {
onTap: onAction, onTap: onAction,
child: Row( child: Row(
children: [ children: [
Text(action ?? '', style: UiTypography.body3r), Text(
action ?? '',
style: UiTypography.body3r.textSecondary,
),
const Icon( const Icon(
UiIcons.chevronRight, UiIcons.chevronRight,
size: UiConstants.space4, size: UiConstants.space4,
color: UiColors.primary, color: UiColors.iconSecondary,
), ),
], ],
), ),

View File

@@ -14,6 +14,9 @@ class SectionLayout extends StatelessWidget {
/// Optional action text/widget to display on the right side of the header. /// Optional action text/widget to display on the right side of the header.
final String? action; final String? action;
/// Optional callback when action is tapped.
final VoidCallback? onAction;
/// The main content of the section. /// The main content of the section.
final Widget child; final Widget child;
@@ -25,6 +28,7 @@ class SectionLayout extends StatelessWidget {
const SectionLayout({ const SectionLayout({
this.title, this.title,
this.action, this.action,
this.onAction,
required this.child, required this.child,
this.contentPadding, this.contentPadding,
super.key, super.key,
@@ -41,6 +45,7 @@ class SectionLayout extends StatelessWidget {
child: SectionHeader( child: SectionHeader(
title: title!, title: title!,
action: action, action: action,
onAction: onAction,
), ),
), ),
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),

View File

@@ -1,7 +1,6 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_home/src/presentation/widgets/worker/worker_benefits/circular_progress_painter.dart';
import 'circular_progress_painter.dart';
/// A widget that displays a single benefit item with circular progress. /// A widget that displays a single benefit item with circular progress.
/// ///
@@ -19,16 +18,12 @@ class BenefitItem extends StatelessWidget {
/// The hours already used. /// The hours already used.
final double used; final double used;
/// The color for the progress indicator.
final Color color;
/// Creates a [BenefitItem]. /// Creates a [BenefitItem].
const BenefitItem({ const BenefitItem({
required this.label, required this.label,
required this.remaining, required this.remaining,
required this.total, required this.total,
required this.used, required this.used,
required this.color,
super.key, super.key,
}); });
@@ -44,8 +39,8 @@ class BenefitItem extends StatelessWidget {
child: CustomPaint( child: CustomPaint(
painter: CircularProgressPainter( painter: CircularProgressPainter(
progress: progress, progress: progress,
color: color, color: UiColors.primary,
backgroundColor: const Color(0xFFE2E8F0), backgroundColor: UiColors.primaryInverse,
strokeWidth: 5, strokeWidth: 5,
), ),
child: Center( child: Center(
@@ -54,28 +49,21 @@ class BenefitItem extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
'${remaining.toInt()}/${total.toInt()}', '${remaining.toInt()}/${total.toInt()}',
style: UiTypography.body2b.textPrimary.copyWith( style: UiTypography.body2b
fontSize: 12,
letterSpacing: -0.5,
),
), ),
Text( Text(
'hours', 'hours',
style: UiTypography.footnote2r.textTertiary.copyWith( style: UiTypography.footnote2r.textSecondary,
fontSize: 8,
),
), ),
], ],
), ),
), ),
), ),
), ),
const SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space2),
Text( Text(
label, label,
style: UiTypography.footnote2r.textSecondary.copyWith( style: UiTypography.body2r.textSecondary,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],

View File

@@ -1,14 +1,10 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:staff_home/src/presentation/widgets/worker/worker_benefits/benefit_item.dart';
import 'benefit_item.dart';
import 'benefits_view_all_link.dart';
/// Widget for displaying staff benefits, using design system tokens. /// Widget for displaying staff benefits, using design system tokens.
/// ///
/// Shows a list of benefits with circular progress indicators /// Shows a list of benefits with circular progress indicators.
/// and a link to view all benefits.
class BenefitsWidget extends StatelessWidget { class BenefitsWidget extends StatelessWidget {
/// The list of benefits to display. /// The list of benefits to display.
final List<Benefit> benefits; final List<Benefit> benefits;
@@ -22,37 +18,20 @@ class BenefitsWidget extends StatelessWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Container( return Padding(
padding: const EdgeInsets.all(UiConstants.space5), padding: const EdgeInsets.only(top: UiConstants.space4),
decoration: BoxDecoration( child: Row(
color: UiColors.white, mainAxisAlignment: MainAxisAlignment.spaceBetween,
borderRadius: UiConstants.radiusLg, children: benefits.map((Benefit benefit) {
border: Border.all(color: UiColors.border, width: 0.5), return Expanded(
), child: BenefitItem(
child: Column( label: benefit.title,
children: <Widget>[ remaining: benefit.remainingHours,
const Row( total: benefit.entitlementHours,
mainAxisAlignment: MainAxisAlignment.end, used: benefit.usedHours,
children: <Widget>[ ),
BenefitsViewAllLink(), );
], }).toList(),
),
const SizedBox(height: UiConstants.space6),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: benefits.map((Benefit benefit) {
return Expanded(
child: BenefitItem(
label: benefit.title,
remaining: benefit.remainingHours,
total: benefit.entitlementHours,
used: benefit.usedHours,
color: const Color(0xFF2563EB),
),
);
}).toList(),
),
],
), ),
); );
} }