diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_cubit.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_cubit.dart index bba00dae..8b5f5cd4 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_cubit.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_cubit.dart @@ -25,6 +25,8 @@ class HomeCubit extends Cubit { todayShifts: result.today, tomorrowShifts: result.tomorrow, recommendedShifts: result.recommended, + // Mock profile status for now, ideally fetched from a user repository + isProfileComplete: false, ), ); } catch (e) { @@ -33,4 +35,8 @@ class HomeCubit extends Cubit { ); } } + + void toggleAutoMatch(bool enabled) { + emit(state.copyWith(autoMatchEnabled: enabled)); + } } diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_state.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_state.dart index 156180a2..939c21af 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_state.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/blocs/home_state.dart @@ -7,6 +7,8 @@ class HomeState extends Equatable { final List todayShifts; final List tomorrowShifts; final List recommendedShifts; + final bool autoMatchEnabled; + final bool isProfileComplete; final String? errorMessage; const HomeState({ @@ -14,6 +16,8 @@ class HomeState extends Equatable { this.todayShifts = const [], this.tomorrowShifts = const [], this.recommendedShifts = const [], + this.autoMatchEnabled = false, + this.isProfileComplete = false, this.errorMessage, }); @@ -24,6 +28,8 @@ class HomeState extends Equatable { List? todayShifts, List? tomorrowShifts, List? recommendedShifts, + bool? autoMatchEnabled, + bool? isProfileComplete, String? errorMessage, }) { return HomeState( @@ -31,16 +37,20 @@ class HomeState extends Equatable { todayShifts: todayShifts ?? this.todayShifts, tomorrowShifts: tomorrowShifts ?? this.tomorrowShifts, recommendedShifts: recommendedShifts ?? this.recommendedShifts, + autoMatchEnabled: autoMatchEnabled ?? this.autoMatchEnabled, + isProfileComplete: isProfileComplete ?? this.isProfileComplete, errorMessage: errorMessage ?? this.errorMessage, ); } @override List get props => [ - status, - todayShifts, - tomorrowShifts, - recommendedShifts, - errorMessage, - ]; + status, + todayShifts, + tomorrowShifts, + recommendedShifts, + autoMatchEnabled, + isProfileComplete, + errorMessage, + ]; } diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart index 41d48509..f7cb94e9 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart @@ -1,30 +1,28 @@ import 'package:flutter/material.dart'; -import 'package:lucide_icons/lucide_icons.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:core_localization/core_localization.dart'; import 'package:flutter_modular/flutter_modular.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; import 'package:staff_home/src/presentation/navigation/home_navigator.dart'; -import 'package:staff_home/src/theme.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/pending_payment_card.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/placeholder_banner.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/quick_action_item.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/recommended_shift_card.dart'; +import 'package:staff_home/src/presentation/widgets/home_page/section_header.dart'; import 'package:staff_home/src/presentation/widgets/shift_card.dart'; import 'package:staff_home/src/presentation/widgets/worker/auto_match_toggle.dart'; import 'package:staff_home/src/presentation/widgets/worker/benefits_widget.dart'; import 'package:staff_home/src/presentation/widgets/worker/improve_yourself_widget.dart'; import 'package:staff_home/src/presentation/widgets/worker/more_ways_widget.dart'; -import 'package:staff_home/src/domain/models/shift.dart'; -import 'package:staff_home/src/presentation/blocs/home_cubit.dart'; +import 'package:staff_home/src/theme.dart'; -class WorkerHomePage extends StatefulWidget { +class WorkerHomePage extends StatelessWidget { const WorkerHomePage({super.key}); - @override - State createState() => _WorkerHomePageState(); -} - -class _WorkerHomePageState extends State { - bool _autoMatchEnabled = false; - final bool _isProfileComplete = false; // Added for mock profile completion - @override Widget build(BuildContext context) { final i18n = t.staff.home; @@ -32,7 +30,7 @@ class _WorkerHomePageState extends State { final quickI18n = i18n.quick_actions; final sectionsI18n = i18n.sections; final emptyI18n = i18n.empty_states; - final recI18n = i18n.recommended_card; + return BlocProvider( create: (_) => Modular.get()..loadShifts(), child: Scaffold( @@ -43,37 +41,50 @@ class _WorkerHomePageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildHeader(), - + const HomeHeader(), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ - if (!_isProfileComplete) - _buildPlaceholderBanner( - bannersI18n.complete_profile_title, - bannersI18n.complete_profile_subtitle, - Colors.blue[50]!, - Colors.blue, - onTap: () { - Modular.to.pushWorkerProfile(); - }, - ), + BlocBuilder( + buildWhen: (previous, current) => + previous.isProfileComplete != + current.isProfileComplete, + builder: (context, state) { + if (state.isProfileComplete) return const SizedBox(); + return PlaceholderBanner( + title: bannersI18n.complete_profile_title, + subtitle: bannersI18n.complete_profile_subtitle, + bg: Colors.blue[50]!, + accent: Colors.blue, + onTap: () { + Modular.to.pushWorkerProfile(); + }, + ); + }, + ), const SizedBox(height: 20), - _buildPlaceholderBanner( - bannersI18n.availability_title, - bannersI18n.availability_subtitle, - Colors.orange[50]!, - Colors.orange, + PlaceholderBanner( + title: bannersI18n.availability_title, + subtitle: bannersI18n.availability_subtitle, + bg: Colors.orange[50]!, + accent: Colors.orange, onTap: () => Modular.to.pushAvailability(), ), const SizedBox(height: 20), // Auto Match Toggle - AutoMatchToggle( - enabled: _autoMatchEnabled, - onToggle: (val) => - setState(() => _autoMatchEnabled = val), + BlocBuilder( + buildWhen: (previous, current) => + previous.autoMatchEnabled != current.autoMatchEnabled, + builder: (context, state) { + return AutoMatchToggle( + enabled: state.autoMatchEnabled, + onToggle: (val) => context + .read() + .toggleAutoMatch(val), + ); + }, ), const SizedBox(height: 20), @@ -82,35 +93,31 @@ class _WorkerHomePageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: _buildQuickAction( - context, - LucideIcons.search, - quickI18n.find_shifts, - () => Modular.to.pushShifts(), + child: QuickActionItem( + icon: LucideIcons.search, + label: quickI18n.find_shifts, + onTap: () => Modular.to.pushShifts(), ), ), Expanded( - child: _buildQuickAction( - context, - LucideIcons.calendar, - quickI18n.availability, - () => Modular.to.pushAvailability(), + child: QuickActionItem( + icon: LucideIcons.calendar, + label: quickI18n.availability, + onTap: () => Modular.to.pushAvailability(), ), ), Expanded( - child: _buildQuickAction( - context, - LucideIcons.messageSquare, - quickI18n.messages, - () => Modular.to.pushMessages(), + child: QuickActionItem( + icon: LucideIcons.messageSquare, + label: quickI18n.messages, + onTap: () => Modular.to.pushMessages(), ), ), Expanded( - child: _buildQuickAction( - context, - LucideIcons.dollarSign, - quickI18n.earnings, - () => Modular.to.pushPayments(), + child: QuickActionItem( + icon: LucideIcons.dollarSign, + label: quickI18n.earnings, + onTap: () => Modular.to.pushPayments(), ), ), ], @@ -123,9 +130,9 @@ class _WorkerHomePageState extends State { final shifts = state.todayShifts; return Column( children: [ - _buildSectionHeader( - sectionsI18n.todays_shift, - shifts.isNotEmpty + SectionHeader( + title: sectionsI18n.todays_shift, + action: shifts.isNotEmpty ? sectionsI18n.scheduled_count( count: shifts.length, ) @@ -140,10 +147,11 @@ class _WorkerHomePageState extends State { ), ) else if (shifts.isEmpty) - _buildEmptyState( - emptyI18n.no_shifts_today, - emptyI18n.find_shifts_cta, - () => Modular.to.pushShifts(tab: 'find'), + EmptyStateWidget( + message: emptyI18n.no_shifts_today, + actionLink: emptyI18n.find_shifts_cta, + onAction: () => + Modular.to.pushShifts(tab: 'find'), ) else Column( @@ -168,11 +176,12 @@ class _WorkerHomePageState extends State { final shifts = state.tomorrowShifts; return Column( children: [ - _buildSectionHeader(sectionsI18n.tomorrow, null), + SectionHeader( + title: sectionsI18n.tomorrow, + ), if (shifts.isEmpty) - _buildEmptyState( - emptyI18n.no_shifts_tomorrow, - null, + EmptyStateWidget( + message: emptyI18n.no_shifts_tomorrow, ) else Column( @@ -192,21 +201,20 @@ class _WorkerHomePageState extends State { const SizedBox(height: 24), // Pending Payment Card - _buildPendingPaymentCard(), + const PendingPaymentCard(), const SizedBox(height: 24), // Recommended Shifts - _buildSectionHeader( - sectionsI18n.recommended_for_you, - sectionsI18n.view_all, + SectionHeader( + title: sectionsI18n.recommended_for_you, + action: sectionsI18n.view_all, onAction: () => Modular.to.pushShifts(tab: 'find'), ), BlocBuilder( builder: (context, state) { if (state.recommendedShifts.isEmpty) { - return _buildEmptyState( - emptyI18n.no_recommended_shifts, - null, + return EmptyStateWidget( + message: emptyI18n.no_recommended_shifts, ); } return SizedBox( @@ -217,8 +225,8 @@ class _WorkerHomePageState extends State { clipBehavior: Clip.none, itemBuilder: (context, index) => Padding( padding: const EdgeInsets.only(right: 12), - child: _buildRecommendedCard( - state.recommendedShifts[index], + child: RecommendedShiftCard( + shift: state.recommendedShifts[index], ), ), ), @@ -244,598 +252,4 @@ class _WorkerHomePageState extends State { ), ); } - - Widget _buildSectionHeader( - String title, - String? action, { - VoidCallback? onAction, - }) { - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: AppColors.krowCharcoal, - ), - ), - if (action != null) - if (onAction != null) - GestureDetector( - onTap: onAction, - child: Row( - children: [ - Text( - action, - style: const TextStyle( - color: AppColors.krowBlue, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const Icon( - LucideIcons.chevronRight, - size: 16, - color: AppColors.krowBlue, - ), - ], - ), - ) - else - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: AppColors.krowBlue.withValues(alpha: 0.08), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: AppColors.krowBlue.withValues(alpha: 0.2), - ), - ), - child: Text( - action, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: AppColors.krowBlue, - ), - ), - ), - ], - ), - ); - } - - Widget _buildEmptyState( - String message, - String? actionLink, [ - VoidCallback? onAction, - ]) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFFF1F3F5), - borderRadius: BorderRadius.circular(8), - ), - alignment: Alignment.center, - child: Column( - children: [ - Text( - message, - style: const TextStyle(color: AppColors.krowMuted, fontSize: 14), - ), - if (actionLink != null) - GestureDetector( - onTap: onAction, - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - actionLink, - style: const TextStyle( - color: AppColors.krowBlue, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildHeader() { - final headerI18n = t.staff.home.header; - return Padding( - padding: const EdgeInsets.fromLTRB(20, 24, 20, 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: AppColors.krowBlue.withValues(alpha: 0.2), - width: 2, - ), - ), - child: CircleAvatar( - backgroundColor: AppColors.krowBlue.withValues(alpha: 0.1), - child: const Text( - 'K', - style: TextStyle( - color: AppColors.krowBlue, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - headerI18n.welcome_back, - style: const TextStyle( - color: AppColors.krowMuted, - fontSize: 14, - ), - ), - Text( - headerI18n.user_name_placeholder, - style: const TextStyle( - color: AppColors.krowCharcoal, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ], - ), - Row( - children: [ - GestureDetector( - onTap: () => Modular.to.pushMessages(), - child: Stack( - children: [ - _buildHeaderIcon(LucideIcons.bell), - const Positioned( - top: -2, - right: -2, - child: CircleAvatar( - radius: 8, - backgroundColor: Color(0xFFF04444), - child: Text( - '2', - style: TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - const SizedBox(width: 8), - GestureDetector( - onTap: () => Modular.to.pushWorkerProfile(), - child: _buildHeaderIcon(LucideIcons.settings), - ), - ], - ), - ], - ), - ); - } - - Widget _buildHeaderIcon(IconData icon) { - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 2, - offset: const Offset(0, 1), - ), - ], - ), - child: Icon(icon, color: AppColors.krowMuted, size: 20), - ); - } - - Widget _buildPlaceholderBanner( - String title, - String subtitle, - Color bg, - Color accent, { - VoidCallback? onTap, - }) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: bg, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: accent.withValues(alpha: 0.3)), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: Icon(LucideIcons.star, color: accent, size: 20), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - fontWeight: FontWeight.bold, - color: AppColors.krowCharcoal, - ), - ), - Text( - subtitle, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - ), - ], - ), - ), - Icon(LucideIcons.chevronRight, color: accent), - ], - ), - ), - ); - } - - Widget _buildQuickAction( - BuildContext context, - IconData icon, - String label, - VoidCallback onTap, - ) { - return GestureDetector( - onTap: onTap, - child: Column( - children: [ - Container( - width: 50, - height: 50, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFFF1F5F9)), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.05), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Icon(icon, color: AppColors.krowBlue, size: 24), - ), - const SizedBox(height: 8), - Text( - label, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: AppColors.krowCharcoal, - ), - ), - ], - ), - ); - } - - Widget _buildPendingPaymentCard() { - final pendingI18n = t.staff.home.pending_payment; - return GestureDetector( - onTap: () => Modular.to.pushPayments(), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue[50]!.withValues(alpha: 0.5), Colors.blue[50]!], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.blue[100]!.withValues(alpha: 0.5)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - color: Color(0xFFE8F0FF), - shape: BoxShape.circle, - ), - child: const Icon( - LucideIcons.dollarSign, - color: Color(0xFF0047FF), - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - pendingI18n.title, - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - color: AppColors.krowCharcoal, - ), - overflow: TextOverflow.ellipsis, - ), - Text( - pendingI18n.subtitle, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ], - ), - ), - const Row( - children: [ - Text( - '\$285.00', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - color: Color(0xFF0047FF), - ), - ), - SizedBox(width: 8), - Icon( - LucideIcons.chevronRight, - color: Color(0xFF94A3B8), - size: 20, - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildRecommendedCard(Shift shift) { - final recI18n = t.staff.home.recommended_card; - final duration = 8; - final totalPay = duration * shift.hourlyRate; - return GestureDetector( - onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - recI18n.applied_for(title: shift.title), - ), - backgroundColor: Colors.green, - duration: const Duration(seconds: 2), - ), - ); - }, - child: Container( - width: 300, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColors.krowBorder), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.02), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - Text( - recI18n.act_now, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Color(0xFFDC2626), - ), - ), - const SizedBox(width: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: const Color(0xFFE8F0FF), - borderRadius: BorderRadius.circular(999), - ), - child: Text( - recI18n.one_day, - style: const TextStyle( - fontSize: 10, - fontWeight: FontWeight.w500, - color: Color(0xFF0047FF), - ), - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 44, - height: 44, - decoration: BoxDecoration( - color: const Color(0xFFE8F0FF), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - LucideIcons.calendar, - color: Color(0xFF0047FF), - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - shift.title, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: AppColors.krowCharcoal, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - '\$${totalPay.round()}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: AppColors.krowCharcoal, - ), - ), - ], - ), - const SizedBox(height: 2), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - shift.clientName, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - ), - Text( - '\$${shift.hourlyRate.toStringAsFixed(0)}/hr • ${duration}h', - style: const TextStyle( - fontSize: 10, - color: AppColors.krowMuted, - ), - ), - ], - ), - ], - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - const Icon( - LucideIcons.calendar, - size: 14, - color: AppColors.krowMuted, - ), - const SizedBox(width: 4), - Text( - recI18n.today, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - ), - const SizedBox(width: 12), - const Icon( - LucideIcons.clock, - size: 14, - color: AppColors.krowMuted, - ), - const SizedBox(width: 4), - Text( - recI18n.time_range(start: shift.startTime, end: shift.endTime), - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - const Icon( - LucideIcons.mapPin, - size: 14, - color: AppColors.krowMuted, - ), - const SizedBox(width: 4), - Expanded( - child: Text( - shift.locationAddress ?? shift.location, - style: const TextStyle( - fontSize: 12, - color: AppColors.krowMuted, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ], - ), - ), - ); - } -} +} \ No newline at end of file diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/empty_state_widget.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/empty_state_widget.dart new file mode 100644 index 00000000..85e0185e --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/empty_state_widget.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:staff_home/src/theme.dart'; + +class EmptyStateWidget extends StatelessWidget { + final String message; + final String? actionLink; + final VoidCallback? onAction; + + const EmptyStateWidget({ + super.key, + required this.message, + this.actionLink, + this.onAction, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF1F3F5), + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Column( + children: [ + Text( + message, + style: const TextStyle(color: AppColors.krowMuted, fontSize: 14), + ), + if (actionLink != null) + GestureDetector( + onTap: onAction, + child: Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + actionLink!, + style: const TextStyle( + color: AppColors.krowBlue, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart new file mode 100644 index 00000000..ffddb2f3 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/home_header.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/theme.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:staff_home/src/presentation/navigation/home_navigator.dart'; + +class HomeHeader extends StatelessWidget { + const HomeHeader({super.key}); + + @override + Widget build(BuildContext context) { + final headerI18n = t.staff.home.header; + return Padding( + padding: const EdgeInsets.fromLTRB(20, 24, 20, 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: AppColors.krowBlue.withValues(alpha: 0.2), + width: 2, + ), + ), + child: CircleAvatar( + backgroundColor: AppColors.krowBlue.withValues(alpha: 0.1), + child: const Text( + 'K', + style: TextStyle( + color: AppColors.krowBlue, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + headerI18n.welcome_back, + style: const TextStyle( + color: AppColors.krowMuted, + fontSize: 14, + ), + ), + Text( + headerI18n.user_name_placeholder, + style: const TextStyle( + color: AppColors.krowCharcoal, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + Row( + children: [ + GestureDetector( + onTap: () => Modular.to.pushMessages(), + child: Stack( + children: [ + _buildHeaderIcon(LucideIcons.bell), + const Positioned( + top: -2, + right: -2, + child: CircleAvatar( + radius: 8, + backgroundColor: Color(0xFFF04444), + child: Text( + '2', + style: TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () => Modular.to.pushWorkerProfile(), + child: _buildHeaderIcon(LucideIcons.settings), + ), + ], + ), + ], + ), + ); + } + + Widget _buildHeaderIcon(IconData icon) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: Icon(icon, color: AppColors.krowMuted, size: 20), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart new file mode 100644 index 00000000..e6e2e5c6 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/theme.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:staff_home/src/presentation/navigation/home_navigator.dart'; + +class PendingPaymentCard extends StatelessWidget { + const PendingPaymentCard({super.key}); + + @override + Widget build(BuildContext context) { + final pendingI18n = t.staff.home.pending_payment; + return GestureDetector( + onTap: () => Modular.to.pushPayments(), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.blue[50]!.withValues(alpha: 0.5), Colors.blue[50]!], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.blue[100]!.withValues(alpha: 0.5)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: const BoxDecoration( + color: Color(0xFFE8F0FF), + shape: BoxShape.circle, + ), + child: const Icon( + LucideIcons.dollarSign, + color: Color(0xFF0047FF), + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + pendingI18n.title, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: AppColors.krowCharcoal, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + pendingI18n.subtitle, + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + const Row( + children: [ + Text( + '\$285.00', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: Color(0xFF0047FF), + ), + ), + SizedBox(width: 8), + Icon( + LucideIcons.chevronRight, + color: Color(0xFF94A3B8), + size: 20, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart new file mode 100644 index 00000000..48e760e5 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/placeholder_banner.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/theme.dart'; + +class PlaceholderBanner extends StatelessWidget { + final String title; + final String subtitle; + final Color bg; + final Color accent; + final VoidCallback? onTap; + + const PlaceholderBanner({ + super.key, + required this.title, + required this.subtitle, + required this.bg, + required this.accent, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: accent.withValues(alpha: 0.3)), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: Icon(LucideIcons.star, color: accent, size: 20), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.krowCharcoal, + ), + ), + Text( + subtitle, + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + ), + ], + ), + ), + Icon(LucideIcons.chevronRight, color: accent), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart new file mode 100644 index 00000000..0293f627 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/quick_action_item.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/theme.dart'; + +class QuickActionItem extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onTap; + + const QuickActionItem({ + super.key, + required this.icon, + required this.label, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Column( + children: [ + Container( + width: 50, + height: 50, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFFF1F5F9)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon(icon, color: AppColors.krowBlue, size: 24), + ), + const SizedBox(height: 8), + Text( + label, + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: AppColors.krowCharcoal, + ), + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart new file mode 100644 index 00000000..26a5e028 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/recommended_shift_card.dart @@ -0,0 +1,214 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/domain/models/shift.dart'; +import 'package:staff_home/src/theme.dart'; +import 'package:core_localization/core_localization.dart'; + +class RecommendedShiftCard extends StatelessWidget { + final Shift shift; + + const RecommendedShiftCard({super.key, required this.shift}); + + @override + Widget build(BuildContext context) { + final recI18n = t.staff.home.recommended_card; + final duration = 8; + final totalPay = duration * shift.hourlyRate; + + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + recI18n.applied_for(title: shift.title), + ), + backgroundColor: Colors.green, + duration: const Duration(seconds: 2), + ), + ); + }, + child: Container( + width: 300, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.krowBorder), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.02), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Text( + recI18n.act_now, + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Color(0xFFDC2626), + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: const Color(0xFFE8F0FF), + borderRadius: BorderRadius.circular(999), + ), + child: Text( + recI18n.one_day, + style: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: Color(0xFF0047FF), + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: const Color(0xFFE8F0FF), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + LucideIcons.calendar, + color: Color(0xFF0047FF), + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + shift.title, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.krowCharcoal, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + '\$${totalPay.round()}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.krowCharcoal, + ), + ), + ], + ), + const SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + shift.clientName, + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + ), + Text( + '\$${shift.hourlyRate.toStringAsFixed(0)}/hr • ${duration}h', + style: const TextStyle( + fontSize: 10, + color: AppColors.krowMuted, + ), + ), + ], + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + const Icon( + LucideIcons.calendar, + size: 14, + color: AppColors.krowMuted, + ), + const SizedBox(width: 4), + Text( + recI18n.today, + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + ), + const SizedBox(width: 12), + const Icon( + LucideIcons.clock, + size: 14, + color: AppColors.krowMuted, + ), + const SizedBox(width: 4), + Text( + recI18n.time_range( + start: shift.startTime, + end: shift.endTime, + ), + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + const Icon( + LucideIcons.mapPin, + size: 14, + color: AppColors.krowMuted, + ), + const SizedBox(width: 4), + Expanded( + child: Text( + shift.locationAddress ?? shift.location, + style: const TextStyle( + fontSize: 12, + color: AppColors.krowMuted, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart new file mode 100644 index 00000000..173425a2 --- /dev/null +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/section_header.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:staff_home/src/theme.dart'; + +class SectionHeader extends StatelessWidget { + final String title; + final String? action; + final VoidCallback? onAction; + + const SectionHeader({ + super.key, + required this.title, + this.action, + this.onAction, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: AppColors.krowCharcoal, + ), + ), + if (action != null) + if (onAction != null) + GestureDetector( + onTap: onAction, + child: Row( + children: [ + Text( + action!, + style: const TextStyle( + color: AppColors.krowBlue, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const Icon( + LucideIcons.chevronRight, + size: 16, + color: AppColors.krowBlue, + ), + ], + ), + ) + else + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: AppColors.krowBlue.withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.krowBlue.withValues(alpha: 0.2), + ), + ), + child: Text( + action!, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColors.krowBlue, + ), + ), + ), + ], + ), + ); + } +}