feat: Enhance benefits section and layout for improved user experience
This commit is contained in:
@@ -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),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user