feat: Refactor home cubit and add benefits overview functionality
- Updated import paths for home_cubit.dart to reflect new structure. - Introduced BenefitsOverviewCubit to manage benefits overview page state. - Created BenefitsOverviewState to handle loading, loaded, and error states for benefits. - Implemented HomeCubit to manage home page state, including shifts and benefits. - Added new widgets for benefits overview: BenefitCard, BenefitCardHeader, AccordionHistory, ComplianceBanner, StatChip, and BenefitsOverviewBody. - Implemented custom painter for circular progress indicators. - Enhanced UI components for displaying benefits and their statuses.
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
|
|
||||||
|
part 'benefits_overview_state.dart';
|
||||||
|
|
||||||
|
/// Cubit to manage benefits overview page state.
|
||||||
|
class BenefitsOverviewCubit extends Cubit<BenefitsOverviewState>
|
||||||
|
with BlocErrorHandler<BenefitsOverviewState> {
|
||||||
|
final HomeRepository _repository;
|
||||||
|
|
||||||
|
BenefitsOverviewCubit({required HomeRepository repository})
|
||||||
|
: _repository = repository,
|
||||||
|
super(const BenefitsOverviewState.initial());
|
||||||
|
|
||||||
|
Future<void> loadBenefits() async {
|
||||||
|
if (isClosed) return;
|
||||||
|
emit(state.copyWith(status: BenefitsOverviewStatus.loading));
|
||||||
|
await handleError(
|
||||||
|
emit: emit,
|
||||||
|
action: () async {
|
||||||
|
final benefits = await _repository.getBenefits();
|
||||||
|
if (isClosed) return;
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: BenefitsOverviewStatus.loaded,
|
||||||
|
benefits: benefits,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (String errorKey) {
|
||||||
|
if (isClosed) return state;
|
||||||
|
return state.copyWith(
|
||||||
|
status: BenefitsOverviewStatus.error,
|
||||||
|
errorMessage: errorKey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
part of 'benefits_overview_cubit.dart';
|
||||||
|
|
||||||
|
enum BenefitsOverviewStatus { initial, loading, loaded, error }
|
||||||
|
|
||||||
|
class BenefitsOverviewState extends Equatable {
|
||||||
|
final BenefitsOverviewStatus status;
|
||||||
|
final List<Benefit> benefits;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const BenefitsOverviewState({
|
||||||
|
required this.status,
|
||||||
|
this.benefits = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const BenefitsOverviewState.initial()
|
||||||
|
: this(status: BenefitsOverviewStatus.initial);
|
||||||
|
|
||||||
|
BenefitsOverviewState copyWith({
|
||||||
|
BenefitsOverviewStatus? status,
|
||||||
|
List<Benefit>? benefits,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return BenefitsOverviewState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
benefits: benefits ?? this.benefits,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, benefits, errorMessage];
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import 'package:core_localization/core_localization.dart';
|
import 'package:core_localization/core_localization.dart';
|
||||||
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:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:staff_home/src/presentation/blocs/benefits_overview/benefits_overview_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/benefits_overview_body.dart';
|
||||||
|
|
||||||
/// Page displaying a detailed overview of the worker's benefits.
|
/// Page displaying a detailed overview of the worker's benefits.
|
||||||
class BenefitsOverviewPage extends StatelessWidget {
|
class BenefitsOverviewPage extends StatelessWidget {
|
||||||
@@ -15,24 +13,29 @@ class BenefitsOverviewPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<HomeCubit>.value(
|
return Scaffold(
|
||||||
value: Modular.get<HomeCubit>(),
|
appBar: UiAppBar(
|
||||||
child: Scaffold(
|
title: t.staff.home.benefits.overview.title,
|
||||||
backgroundColor: const Color(0xFFF8FAFC),
|
subtitle: t.staff.home.benefits.overview.subtitle,
|
||||||
appBar: _buildAppBar(context),
|
showBackButton: true,
|
||||||
body: BlocBuilder<HomeCubit, HomeState>(
|
),
|
||||||
|
body: BlocProvider<BenefitsOverviewCubit>(
|
||||||
|
create: (context) =>
|
||||||
|
Modular.get<BenefitsOverviewCubit>()..loadBenefits(),
|
||||||
|
child: BlocBuilder<BenefitsOverviewCubit, BenefitsOverviewState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.status == HomeStatus.loading ||
|
if (state.status == BenefitsOverviewStatus.loading ||
|
||||||
state.status == HomeStatus.initial) {
|
state.status == BenefitsOverviewStatus.initial) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.status == HomeStatus.error) {
|
if (state.status == BenefitsOverviewStatus.error) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(UiConstants.space6),
|
padding: const EdgeInsets.all(UiConstants.space6),
|
||||||
child: Text(
|
child: Text(
|
||||||
state.errorMessage ?? t.staff.home.benefits.overview.subtitle,
|
state.errorMessage ??
|
||||||
|
t.staff.home.benefits.overview.subtitle,
|
||||||
style: UiTypography.body1r.textSecondary,
|
style: UiTypography.body1r.textSecondary,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -40,8 +43,7 @@ class BenefitsOverviewPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final benefits = state.benefits;
|
if (state.benefits.isEmpty) {
|
||||||
if (benefits.isEmpty) {
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(UiConstants.space6),
|
padding: const EdgeInsets.all(UiConstants.space6),
|
||||||
@@ -54,400 +56,10 @@ class BenefitsOverviewPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return BenefitsOverviewBody(benefits: state.benefits);
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: UiConstants.space4,
|
|
||||||
right: UiConstants.space4,
|
|
||||||
top: UiConstants.space6,
|
|
||||||
bottom: 120,
|
|
||||||
),
|
|
||||||
itemCount: benefits.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: UiConstants.space4),
|
|
||||||
child: _BenefitCard(benefit: benefits[index]),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferredSizeWidget _buildAppBar(BuildContext context) {
|
|
||||||
return AppBar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconPrimary),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
|
||||||
title: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
t.staff.home.benefits.overview.title,
|
|
||||||
style: UiTypography.title2b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
t.staff.home.benefits.overview.subtitle,
|
|
||||||
style: UiTypography.footnote2r.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(1),
|
|
||||||
child: Container(color: UiColors.border.withOpacity(0.5), height: 1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BenefitCard extends StatelessWidget {
|
|
||||||
final Benefit benefit;
|
|
||||||
|
|
||||||
const _BenefitCard({required this.benefit});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final bool isSickLeave = benefit.title.toLowerCase().contains('sick');
|
|
||||||
final bool isVacation = benefit.title.toLowerCase().contains('vacation');
|
|
||||||
final bool isHolidays = benefit.title.toLowerCase().contains('holiday');
|
|
||||||
|
|
||||||
final i18n = t.staff.home.benefits.overview;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: UiConstants.radiusLg,
|
|
||||||
border: Border.all(color: UiColors.border.withOpacity(0.5)),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: UiColors.black.withOpacity(0.02),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
_buildProgressCircle(),
|
|
||||||
const SizedBox(width: UiConstants.space4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
benefit.title,
|
|
||||||
style: UiTypography.body1b.textPrimary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_getSubtitle(benefit.title),
|
|
||||||
style: UiTypography.footnote2r.textSecondary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
_buildStatsRow(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
if (isSickLeave) ...[
|
|
||||||
_AccordionHistory(label: i18n.sick_leave_history),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
],
|
|
||||||
if (isVacation || isHolidays) ...[
|
|
||||||
_buildComplianceBanner(i18n.compliance_banner),
|
|
||||||
const SizedBox(height: UiConstants.space6),
|
|
||||||
],
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: UiButton.primary(
|
|
||||||
text: i18n.request_payment(benefit: benefit.title),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: const Color(0xFF0038A8),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
// TODO: Implement payment request
|
|
||||||
UiSnackbar.show(context, message: i18n.request_submitted(benefit: benefit.title), type: UiSnackbarType.success);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildProgressCircle() {
|
|
||||||
final double progress = benefit.entitlementHours > 0
|
|
||||||
? (benefit.remainingHours / benefit.entitlementHours)
|
|
||||||
: 0.0;
|
|
||||||
|
|
||||||
final bool isSickLeave = benefit.title.toLowerCase().contains('sick');
|
|
||||||
final Color circleColor = isSickLeave ? const Color(0xFF2563EB) : const Color(0xFF10B981);
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: 72,
|
|
||||||
height: 72,
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: _CircularProgressPainter(
|
|
||||||
progress: progress,
|
|
||||||
color: circleColor,
|
|
||||||
backgroundColor: const Color(0xFFE2E8F0),
|
|
||||||
strokeWidth: 6,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${benefit.remainingHours.toInt()}/${benefit.entitlementHours.toInt()}',
|
|
||||||
style: UiTypography.body2b.textPrimary.copyWith(fontSize: 14),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
t.client_billing.hours_suffix,
|
|
||||||
style: UiTypography.footnote2r.textTertiary.copyWith(fontSize: 9),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatsRow() {
|
|
||||||
final i18n = t.staff.home.benefits.overview;
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
_buildStatChip(
|
|
||||||
i18n.entitlement,
|
|
||||||
'${benefit.entitlementHours.toInt()}',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_buildStatChip(
|
|
||||||
i18n.used,
|
|
||||||
'${benefit.usedHours.toInt()}',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
_buildStatChip(
|
|
||||||
i18n.remaining,
|
|
||||||
'${benefit.remainingHours.toInt()}',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatChip(String label, String value) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF1F5F9),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: UiTypography.footnote2r.textTertiary.copyWith(
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'$value ${t.staff.home.benefits.overview.hours}',
|
|
||||||
style: UiTypography.footnote2b.textPrimary.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getSubtitle(String title) {
|
|
||||||
final i18n = t.staff.home.benefits.overview;
|
|
||||||
if (title.toLowerCase().contains('sick')) {
|
|
||||||
return i18n.sick_leave_subtitle;
|
|
||||||
} else if (title.toLowerCase().contains('vacation')) {
|
|
||||||
return i18n.vacation_subtitle;
|
|
||||||
} else if (title.toLowerCase().contains('holiday')) {
|
|
||||||
return i18n.holidays_subtitle;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildComplianceBanner(String text) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFECFDF5),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Icon(UiIcons.checkCircle, size: 16, color: Color(0xFF10B981)),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
text,
|
|
||||||
style: UiTypography.footnote1r.copyWith(
|
|
||||||
color: const Color(0xFF065F46),
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CircularProgressPainter extends CustomPainter {
|
|
||||||
final double progress;
|
|
||||||
final Color color;
|
|
||||||
final Color backgroundColor;
|
|
||||||
final double strokeWidth;
|
|
||||||
|
|
||||||
_CircularProgressPainter({
|
|
||||||
required this.progress,
|
|
||||||
required this.color,
|
|
||||||
required this.backgroundColor,
|
|
||||||
required this.strokeWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final center = Offset(size.width / 2, size.height / 2);
|
|
||||||
final radius = (size.width - strokeWidth) / 2;
|
|
||||||
|
|
||||||
final backgroundPaint = Paint()
|
|
||||||
..color = backgroundColor
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = strokeWidth;
|
|
||||||
canvas.drawCircle(center, radius, backgroundPaint);
|
|
||||||
|
|
||||||
final progressPaint = Paint()
|
|
||||||
..color = color
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..strokeWidth = strokeWidth
|
|
||||||
..strokeCap = StrokeCap.round;
|
|
||||||
final sweepAngle = 2 * math.pi * progress;
|
|
||||||
canvas.drawArc(
|
|
||||||
Rect.fromCircle(center: center, radius: radius),
|
|
||||||
-math.pi / 2,
|
|
||||||
sweepAngle,
|
|
||||||
false,
|
|
||||||
progressPaint,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AccordionHistory extends StatefulWidget {
|
|
||||||
final String label;
|
|
||||||
|
|
||||||
const _AccordionHistory({required this.label});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_AccordionHistory> createState() => _AccordionHistoryState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AccordionHistoryState extends State<_AccordionHistory> {
|
|
||||||
bool _isExpanded = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Divider(height: 1, color: Color(0xFFE2E8F0)),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_isExpanded = !_isExpanded;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.label,
|
|
||||||
style: UiTypography.footnote2b.textSecondary.copyWith(
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
fontSize: 11,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
|
||||||
size: 16,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_isExpanded) ...[
|
|
||||||
_buildHistoryItem('1 Jan, 2024', 'Pending', const Color(0xFFF1F5F9), const Color(0xFF64748B)),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
_buildHistoryItem('28 Jan, 2024', 'Submitted', const Color(0xFFECFDF5), const Color(0xFF10B981)),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
_buildHistoryItem('5 Feb, 2024', 'Submitted', const Color(0xFFECFDF5), const Color(0xFF10B981)),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
_buildHistoryItem('28 Jan, 2024', 'Submitted', const Color(0xFFECFDF5), const Color(0xFF10B981)),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
_buildHistoryItem('5 Feb, 2024', 'Submitted', const Color(0xFFECFDF5), const Color(0xFF10B981)),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHistoryItem(String date, String status, Color bgColor, Color textColor) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
date,
|
|
||||||
style: UiTypography.footnote1r.textSecondary.copyWith(
|
|
||||||
fontSize: 12,
|
|
||||||
color: const Color(0xFF64748B),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: bgColor,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: status == 'Pending' ? Border.all(color: const Color(0xFFE2E8F0)) : null,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
status,
|
|
||||||
style: UiTypography.footnote2m.copyWith(
|
|
||||||
color: textColor,
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.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/home_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/benefits_section.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/benefits_section.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/full_width_divider.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/full_width_divider.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart';
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Widget displaying collapsible benefit history.
|
||||||
|
class AccordionHistory extends StatefulWidget {
|
||||||
|
/// The label for the accordion header.
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// Creates an [AccordionHistory].
|
||||||
|
const AccordionHistory({required this.label, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccordionHistory> createState() => _AccordionHistoryState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccordionHistoryState extends State<AccordionHistory> {
|
||||||
|
bool _isExpanded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(height: 1, color: Color(0xFFE2E8F0)),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isExpanded = !_isExpanded;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.label,
|
||||||
|
style: UiTypography.footnote2b.textSecondary.copyWith(
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
_isExpanded ? UiIcons.chevronUp : UiIcons.chevronDown,
|
||||||
|
size: 16,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_isExpanded) ...[
|
||||||
|
_HistoryItem(
|
||||||
|
date: '1 Jan, 2024',
|
||||||
|
status: 'Pending',
|
||||||
|
bgColor: const Color(0xFFF1F5F9),
|
||||||
|
textColor: const Color(0xFF64748B),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_HistoryItem(
|
||||||
|
date: '28 Jan, 2024',
|
||||||
|
status: 'Submitted',
|
||||||
|
bgColor: const Color(0xFFECFDF5),
|
||||||
|
textColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_HistoryItem(
|
||||||
|
date: '5 Feb, 2024',
|
||||||
|
status: 'Submitted',
|
||||||
|
bgColor: const Color(0xFFECFDF5),
|
||||||
|
textColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_HistoryItem(
|
||||||
|
date: '28 Jan, 2024',
|
||||||
|
status: 'Submitted',
|
||||||
|
bgColor: const Color(0xFFECFDF5),
|
||||||
|
textColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
_HistoryItem(
|
||||||
|
date: '5 Feb, 2024',
|
||||||
|
status: 'Submitted',
|
||||||
|
bgColor: const Color(0xFFECFDF5),
|
||||||
|
textColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistoryItem extends StatelessWidget {
|
||||||
|
final String date;
|
||||||
|
final String status;
|
||||||
|
final Color bgColor;
|
||||||
|
final Color textColor;
|
||||||
|
|
||||||
|
const _HistoryItem({
|
||||||
|
required this.date,
|
||||||
|
required this.status,
|
||||||
|
required this.bgColor,
|
||||||
|
required this.textColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
date,
|
||||||
|
style: UiTypography.footnote1r.textSecondary.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: const Color(0xFF64748B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: status == 'Pending'
|
||||||
|
? Border.all(color: const Color(0xFFE2E8F0))
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
status,
|
||||||
|
style: UiTypography.footnote2m.copyWith(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/accordion_history.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_card_header.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/compliance_banner.dart';
|
||||||
|
|
||||||
|
/// Card widget displaying detailed benefit information.
|
||||||
|
class BenefitCard extends StatelessWidget {
|
||||||
|
/// The benefit to display.
|
||||||
|
final Benefit benefit;
|
||||||
|
|
||||||
|
/// Creates a [BenefitCard].
|
||||||
|
const BenefitCard({required this.benefit, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final bool isSickLeave = benefit.title.toLowerCase().contains('sick');
|
||||||
|
final bool isVacation = benefit.title.toLowerCase().contains('vacation');
|
||||||
|
final bool isHolidays = benefit.title.toLowerCase().contains('holiday');
|
||||||
|
|
||||||
|
final i18n = t.staff.home.benefits.overview;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: UiConstants.radiusLg,
|
||||||
|
border: Border.all(color: UiColors.border.withOpacity(0.5)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: UiColors.black.withOpacity(0.02),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
BenefitCardHeader(benefit: benefit),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
if (isSickLeave) ...[
|
||||||
|
AccordionHistory(label: i18n.sick_leave_history),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
],
|
||||||
|
if (isVacation || isHolidays) ...[
|
||||||
|
ComplianceBanner(text: i18n.compliance_banner),
|
||||||
|
const SizedBox(height: UiConstants.space6),
|
||||||
|
],
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: UiButton.primary(
|
||||||
|
text: i18n.request_payment(benefit: benefit.title),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF0038A8),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Implement payment request
|
||||||
|
UiSnackbar.show(
|
||||||
|
context,
|
||||||
|
message: i18n.request_submitted(benefit: benefit.title),
|
||||||
|
type: UiSnackbarType.success,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/circular_progress_painter.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/stat_chip.dart';
|
||||||
|
|
||||||
|
/// Header section of a benefit card showing progress circle, title, and stats.
|
||||||
|
class BenefitCardHeader extends StatelessWidget {
|
||||||
|
/// The benefit to display.
|
||||||
|
final Benefit benefit;
|
||||||
|
|
||||||
|
/// Creates a [BenefitCardHeader].
|
||||||
|
const BenefitCardHeader({required this.benefit, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.benefits.overview;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
_buildProgressCircle(),
|
||||||
|
const SizedBox(width: UiConstants.space4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
benefit.title,
|
||||||
|
style: UiTypography.body1b.textPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_getSubtitle(benefit.title),
|
||||||
|
style: UiTypography.footnote2r.textSecondary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
_buildStatsRow(i18n),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProgressCircle() {
|
||||||
|
final double progress = benefit.entitlementHours > 0
|
||||||
|
? (benefit.remainingHours / benefit.entitlementHours)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
final bool isSickLeave = benefit.title.toLowerCase().contains('sick');
|
||||||
|
final Color circleColor =
|
||||||
|
isSickLeave ? const Color(0xFF2563EB) : const Color(0xFF10B981);
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: CircularProgressPainter(
|
||||||
|
progress: progress,
|
||||||
|
color: circleColor,
|
||||||
|
backgroundColor: const Color(0xFFE2E8F0),
|
||||||
|
strokeWidth: 6,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${benefit.remainingHours.toInt()}/${benefit.entitlementHours.toInt()}',
|
||||||
|
style: UiTypography.body2b.textPrimary.copyWith(fontSize: 14),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
t.client_billing.hours_suffix,
|
||||||
|
style: UiTypography.footnote2r.textTertiary.copyWith(
|
||||||
|
fontSize: 9,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatsRow(dynamic i18n) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
StatChip(
|
||||||
|
label: i18n.entitlement,
|
||||||
|
value: '${benefit.entitlementHours.toInt()}',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
StatChip(
|
||||||
|
label: i18n.used,
|
||||||
|
value: '${benefit.usedHours.toInt()}',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
StatChip(
|
||||||
|
label: i18n.remaining,
|
||||||
|
value: '${benefit.remainingHours.toInt()}',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSubtitle(String title) {
|
||||||
|
final i18n = t.staff.home.benefits.overview;
|
||||||
|
if (title.toLowerCase().contains('sick')) {
|
||||||
|
return i18n.sick_leave_subtitle;
|
||||||
|
} else if (title.toLowerCase().contains('vacation')) {
|
||||||
|
return i18n.vacation_subtitle;
|
||||||
|
} else if (title.toLowerCase().contains('holiday')) {
|
||||||
|
return i18n.holidays_subtitle;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:staff_home/src/presentation/widgets/benefits_overview/benefit_card.dart';
|
||||||
|
|
||||||
|
/// Body widget displaying a list of benefit cards.
|
||||||
|
class BenefitsOverviewBody extends StatelessWidget {
|
||||||
|
/// The list of benefits to display.
|
||||||
|
final List<Benefit> benefits;
|
||||||
|
|
||||||
|
/// Creates a [BenefitsOverviewBody].
|
||||||
|
const BenefitsOverviewBody({required this.benefits, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: UiConstants.space4,
|
||||||
|
right: UiConstants.space4,
|
||||||
|
top: UiConstants.space6,
|
||||||
|
bottom: 120,
|
||||||
|
),
|
||||||
|
itemCount: benefits.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: UiConstants.space4),
|
||||||
|
child: BenefitCard(benefit: benefits[index]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Custom painter for circular progress indicators.
|
||||||
|
class CircularProgressPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
final Color color;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final double strokeWidth;
|
||||||
|
|
||||||
|
CircularProgressPainter({
|
||||||
|
required this.progress,
|
||||||
|
required this.color,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.strokeWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
|
final radius = (size.width - strokeWidth) / 2;
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = backgroundColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth;
|
||||||
|
canvas.drawCircle(center, radius, backgroundPaint);
|
||||||
|
|
||||||
|
final progressPaint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
final sweepAngle = 2 * math.pi * progress;
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
|
-math.pi / 2,
|
||||||
|
sweepAngle,
|
||||||
|
false,
|
||||||
|
progressPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Widget displaying a compliance information banner.
|
||||||
|
class ComplianceBanner extends StatelessWidget {
|
||||||
|
/// The text to display in the banner.
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
/// Creates a [ComplianceBanner].
|
||||||
|
const ComplianceBanner({
|
||||||
|
required this.text,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFECFDF5),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
UiIcons.checkCircle,
|
||||||
|
size: 16,
|
||||||
|
color: Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: UiTypography.footnote1r.copyWith(
|
||||||
|
color: const Color(0xFF065F46),
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Widget displaying a single statistic chip.
|
||||||
|
class StatChip extends StatelessWidget {
|
||||||
|
/// The label for the stat (e.g., "Entitlement", "Used", "Remaining").
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
/// The numeric value to display.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
/// Creates a [StatChip].
|
||||||
|
const StatChip({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF1F5F9),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: UiTypography.footnote2r.textTertiary.copyWith(fontSize: 10),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'$value ${t.staff.home.benefits.overview.hours}',
|
||||||
|
style: UiTypography.footnote2b.textPrimary.copyWith(fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.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/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';
|
||||||
import 'package:staff_home/src/presentation/widgets/worker/worker_benefits/benefits_widget.dart';
|
import 'package:staff_home/src/presentation/widgets/worker/worker_benefits/benefits_widget.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class RecommendedShiftCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'\$${shift.hourlyRate.toStringAsFixed(0)}/hr',
|
'\$${shift.hourlyRate.toStringAsFixed(0)}/hr',
|
||||||
style: UiTypography.body3r,
|
style: UiTypography.body3r.textSecondary,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:design_system/design_system.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:staff_home/src/presentation/blocs/home_cubit.dart';
|
import 'package:staff_home/src/presentation/blocs/home/home_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/recommended_shift_card.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/recommended_shift_card.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';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.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/home_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.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';
|
||||||
import 'package:staff_home/src/presentation/widgets/shift_card.dart';
|
import 'package:staff_home/src/presentation/widgets/shift_card.dart';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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:staff_home/src/presentation/blocs/home_cubit.dart';
|
import 'package:staff_home/src/presentation/blocs/home/home_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart';
|
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.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';
|
||||||
import 'package:staff_home/src/presentation/widgets/shift_card.dart';
|
import 'package:staff_home/src/presentation/widgets/shift_card.dart';
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import 'package:krow_core/core.dart';
|
|||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:staff_home/src/data/repositories/home_repository_impl.dart';
|
import 'package:staff_home/src/data/repositories/home_repository_impl.dart';
|
||||||
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
|
import 'package:staff_home/src/presentation/blocs/benefits_overview/benefits_overview_cubit.dart';
|
||||||
|
import 'package:staff_home/src/presentation/blocs/home/home_cubit.dart';
|
||||||
import 'package:staff_home/src/presentation/pages/benefits_overview_page.dart';
|
import 'package:staff_home/src/presentation/pages/benefits_overview_page.dart';
|
||||||
import 'package:staff_home/src/presentation/pages/worker_home_page.dart';
|
import 'package:staff_home/src/presentation/pages/worker_home_page.dart';
|
||||||
|
|
||||||
@@ -31,13 +32,18 @@ class StaffHomeModule extends Module {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Presentation layer - Cubit
|
// Presentation layer - Cubits
|
||||||
i.addSingleton(
|
i.addSingleton(
|
||||||
() => HomeCubit(
|
() => HomeCubit(
|
||||||
repository: i.get<HomeRepository>(),
|
repository: i.get<HomeRepository>(),
|
||||||
getProfileCompletion: i.get<GetProfileCompletionUseCase>(),
|
getProfileCompletion: i.get<GetProfileCompletionUseCase>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Cubit for benefits overview page
|
||||||
|
i.addLazySingleton(
|
||||||
|
() => BenefitsOverviewCubit(repository: i.get<HomeRepository>()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
Reference in New Issue
Block a user