refactor(staff_home): refactor worker home page
- Extracted widgets to smaller components in presentation/widgets/home_page - Converted WorkerHomePage to StatelessWidget - Moved local state (autoMatchEnabled) to HomeCubit - Improved code organization and readability
This commit is contained in:
@@ -25,6 +25,8 @@ class HomeCubit extends Cubit<HomeState> {
|
||||
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<HomeState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void toggleAutoMatch(bool enabled) {
|
||||
emit(state.copyWith(autoMatchEnabled: enabled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ class HomeState extends Equatable {
|
||||
final List<Shift> todayShifts;
|
||||
final List<Shift> tomorrowShifts;
|
||||
final List<Shift> 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<Shift>? todayShifts,
|
||||
List<Shift>? tomorrowShifts,
|
||||
List<Shift>? 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<Object?> get props => [
|
||||
status,
|
||||
todayShifts,
|
||||
tomorrowShifts,
|
||||
recommendedShifts,
|
||||
errorMessage,
|
||||
];
|
||||
status,
|
||||
todayShifts,
|
||||
tomorrowShifts,
|
||||
recommendedShifts,
|
||||
autoMatchEnabled,
|
||||
isProfileComplete,
|
||||
errorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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<WorkerHomePage> createState() => _WorkerHomePageState();
|
||||
}
|
||||
|
||||
class _WorkerHomePageState extends State<WorkerHomePage> {
|
||||
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<WorkerHomePage> {
|
||||
final quickI18n = i18n.quick_actions;
|
||||
final sectionsI18n = i18n.sections;
|
||||
final emptyI18n = i18n.empty_states;
|
||||
final recI18n = i18n.recommended_card;
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => Modular.get<HomeCubit>()..loadShifts(),
|
||||
child: Scaffold(
|
||||
@@ -43,37 +41,50 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
||||
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<HomeCubit, HomeState>(
|
||||
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<HomeCubit, HomeState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.autoMatchEnabled != current.autoMatchEnabled,
|
||||
builder: (context, state) {
|
||||
return AutoMatchToggle(
|
||||
enabled: state.autoMatchEnabled,
|
||||
onToggle: (val) => context
|
||||
.read<HomeCubit>()
|
||||
.toggleAutoMatch(val),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -82,35 +93,31 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
||||
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<WorkerHomePage> {
|
||||
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<WorkerHomePage> {
|
||||
),
|
||||
)
|
||||
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<WorkerHomePage> {
|
||||
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<WorkerHomePage> {
|
||||
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<HomeCubit, HomeState>(
|
||||
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<WorkerHomePage> {
|
||||
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<WorkerHomePage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user