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,
|
todayShifts: result.today,
|
||||||
tomorrowShifts: result.tomorrow,
|
tomorrowShifts: result.tomorrow,
|
||||||
recommendedShifts: result.recommended,
|
recommendedShifts: result.recommended,
|
||||||
|
// Mock profile status for now, ideally fetched from a user repository
|
||||||
|
isProfileComplete: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} 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> todayShifts;
|
||||||
final List<Shift> tomorrowShifts;
|
final List<Shift> tomorrowShifts;
|
||||||
final List<Shift> recommendedShifts;
|
final List<Shift> recommendedShifts;
|
||||||
|
final bool autoMatchEnabled;
|
||||||
|
final bool isProfileComplete;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const HomeState({
|
const HomeState({
|
||||||
@@ -14,6 +16,8 @@ class HomeState extends Equatable {
|
|||||||
this.todayShifts = const [],
|
this.todayShifts = const [],
|
||||||
this.tomorrowShifts = const [],
|
this.tomorrowShifts = const [],
|
||||||
this.recommendedShifts = const [],
|
this.recommendedShifts = const [],
|
||||||
|
this.autoMatchEnabled = false,
|
||||||
|
this.isProfileComplete = false,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,6 +28,8 @@ class HomeState extends Equatable {
|
|||||||
List<Shift>? todayShifts,
|
List<Shift>? todayShifts,
|
||||||
List<Shift>? tomorrowShifts,
|
List<Shift>? tomorrowShifts,
|
||||||
List<Shift>? recommendedShifts,
|
List<Shift>? recommendedShifts,
|
||||||
|
bool? autoMatchEnabled,
|
||||||
|
bool? isProfileComplete,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return HomeState(
|
return HomeState(
|
||||||
@@ -31,16 +37,20 @@ class HomeState extends Equatable {
|
|||||||
todayShifts: todayShifts ?? this.todayShifts,
|
todayShifts: todayShifts ?? this.todayShifts,
|
||||||
tomorrowShifts: tomorrowShifts ?? this.tomorrowShifts,
|
tomorrowShifts: tomorrowShifts ?? this.tomorrowShifts,
|
||||||
recommendedShifts: recommendedShifts ?? this.recommendedShifts,
|
recommendedShifts: recommendedShifts ?? this.recommendedShifts,
|
||||||
|
autoMatchEnabled: autoMatchEnabled ?? this.autoMatchEnabled,
|
||||||
|
isProfileComplete: isProfileComplete ?? this.isProfileComplete,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
status,
|
status,
|
||||||
todayShifts,
|
todayShifts,
|
||||||
tomorrowShifts,
|
tomorrowShifts,
|
||||||
recommendedShifts,
|
recommendedShifts,
|
||||||
errorMessage,
|
autoMatchEnabled,
|
||||||
];
|
isProfileComplete,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:core_localization/core_localization.dart';
|
|
||||||
import 'package:flutter_modular/flutter_modular.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/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/shift_card.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/worker/auto_match_toggle.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/benefits_widget.dart';
|
||||||
import 'package:staff_home/src/presentation/widgets/worker/improve_yourself_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/presentation/widgets/worker/more_ways_widget.dart';
|
||||||
import 'package:staff_home/src/domain/models/shift.dart';
|
import 'package:staff_home/src/theme.dart';
|
||||||
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
|
|
||||||
|
|
||||||
class WorkerHomePage extends StatefulWidget {
|
class WorkerHomePage extends StatelessWidget {
|
||||||
const WorkerHomePage({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final i18n = t.staff.home;
|
final i18n = t.staff.home;
|
||||||
@@ -32,7 +30,7 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
final quickI18n = i18n.quick_actions;
|
final quickI18n = i18n.quick_actions;
|
||||||
final sectionsI18n = i18n.sections;
|
final sectionsI18n = i18n.sections;
|
||||||
final emptyI18n = i18n.empty_states;
|
final emptyI18n = i18n.empty_states;
|
||||||
final recI18n = i18n.recommended_card;
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => Modular.get<HomeCubit>()..loadShifts(),
|
create: (_) => Modular.get<HomeCubit>()..loadShifts(),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@@ -43,37 +41,50 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
const HomeHeader(),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (!_isProfileComplete)
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
_buildPlaceholderBanner(
|
buildWhen: (previous, current) =>
|
||||||
bannersI18n.complete_profile_title,
|
previous.isProfileComplete !=
|
||||||
bannersI18n.complete_profile_subtitle,
|
current.isProfileComplete,
|
||||||
Colors.blue[50]!,
|
builder: (context, state) {
|
||||||
Colors.blue,
|
if (state.isProfileComplete) return const SizedBox();
|
||||||
onTap: () {
|
return PlaceholderBanner(
|
||||||
Modular.to.pushWorkerProfile();
|
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),
|
const SizedBox(height: 20),
|
||||||
_buildPlaceholderBanner(
|
PlaceholderBanner(
|
||||||
bannersI18n.availability_title,
|
title: bannersI18n.availability_title,
|
||||||
bannersI18n.availability_subtitle,
|
subtitle: bannersI18n.availability_subtitle,
|
||||||
Colors.orange[50]!,
|
bg: Colors.orange[50]!,
|
||||||
Colors.orange,
|
accent: Colors.orange,
|
||||||
onTap: () => Modular.to.pushAvailability(),
|
onTap: () => Modular.to.pushAvailability(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Auto Match Toggle
|
// Auto Match Toggle
|
||||||
AutoMatchToggle(
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
enabled: _autoMatchEnabled,
|
buildWhen: (previous, current) =>
|
||||||
onToggle: (val) =>
|
previous.autoMatchEnabled != current.autoMatchEnabled,
|
||||||
setState(() => _autoMatchEnabled = val),
|
builder: (context, state) {
|
||||||
|
return AutoMatchToggle(
|
||||||
|
enabled: state.autoMatchEnabled,
|
||||||
|
onToggle: (val) => context
|
||||||
|
.read<HomeCubit>()
|
||||||
|
.toggleAutoMatch(val),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
@@ -82,35 +93,31 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickAction(
|
child: QuickActionItem(
|
||||||
context,
|
icon: LucideIcons.search,
|
||||||
LucideIcons.search,
|
label: quickI18n.find_shifts,
|
||||||
quickI18n.find_shifts,
|
onTap: () => Modular.to.pushShifts(),
|
||||||
() => Modular.to.pushShifts(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickAction(
|
child: QuickActionItem(
|
||||||
context,
|
icon: LucideIcons.calendar,
|
||||||
LucideIcons.calendar,
|
label: quickI18n.availability,
|
||||||
quickI18n.availability,
|
onTap: () => Modular.to.pushAvailability(),
|
||||||
() => Modular.to.pushAvailability(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickAction(
|
child: QuickActionItem(
|
||||||
context,
|
icon: LucideIcons.messageSquare,
|
||||||
LucideIcons.messageSquare,
|
label: quickI18n.messages,
|
||||||
quickI18n.messages,
|
onTap: () => Modular.to.pushMessages(),
|
||||||
() => Modular.to.pushMessages(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildQuickAction(
|
child: QuickActionItem(
|
||||||
context,
|
icon: LucideIcons.dollarSign,
|
||||||
LucideIcons.dollarSign,
|
label: quickI18n.earnings,
|
||||||
quickI18n.earnings,
|
onTap: () => Modular.to.pushPayments(),
|
||||||
() => Modular.to.pushPayments(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -123,9 +130,9 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
final shifts = state.todayShifts;
|
final shifts = state.todayShifts;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_buildSectionHeader(
|
SectionHeader(
|
||||||
sectionsI18n.todays_shift,
|
title: sectionsI18n.todays_shift,
|
||||||
shifts.isNotEmpty
|
action: shifts.isNotEmpty
|
||||||
? sectionsI18n.scheduled_count(
|
? sectionsI18n.scheduled_count(
|
||||||
count: shifts.length,
|
count: shifts.length,
|
||||||
)
|
)
|
||||||
@@ -140,10 +147,11 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (shifts.isEmpty)
|
else if (shifts.isEmpty)
|
||||||
_buildEmptyState(
|
EmptyStateWidget(
|
||||||
emptyI18n.no_shifts_today,
|
message: emptyI18n.no_shifts_today,
|
||||||
emptyI18n.find_shifts_cta,
|
actionLink: emptyI18n.find_shifts_cta,
|
||||||
() => Modular.to.pushShifts(tab: 'find'),
|
onAction: () =>
|
||||||
|
Modular.to.pushShifts(tab: 'find'),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Column(
|
Column(
|
||||||
@@ -168,11 +176,12 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
final shifts = state.tomorrowShifts;
|
final shifts = state.tomorrowShifts;
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
_buildSectionHeader(sectionsI18n.tomorrow, null),
|
SectionHeader(
|
||||||
|
title: sectionsI18n.tomorrow,
|
||||||
|
),
|
||||||
if (shifts.isEmpty)
|
if (shifts.isEmpty)
|
||||||
_buildEmptyState(
|
EmptyStateWidget(
|
||||||
emptyI18n.no_shifts_tomorrow,
|
message: emptyI18n.no_shifts_tomorrow,
|
||||||
null,
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Column(
|
Column(
|
||||||
@@ -192,21 +201,20 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Pending Payment Card
|
// Pending Payment Card
|
||||||
_buildPendingPaymentCard(),
|
const PendingPaymentCard(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Recommended Shifts
|
// Recommended Shifts
|
||||||
_buildSectionHeader(
|
SectionHeader(
|
||||||
sectionsI18n.recommended_for_you,
|
title: sectionsI18n.recommended_for_you,
|
||||||
sectionsI18n.view_all,
|
action: sectionsI18n.view_all,
|
||||||
onAction: () => Modular.to.pushShifts(tab: 'find'),
|
onAction: () => Modular.to.pushShifts(tab: 'find'),
|
||||||
),
|
),
|
||||||
BlocBuilder<HomeCubit, HomeState>(
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.recommendedShifts.isEmpty) {
|
if (state.recommendedShifts.isEmpty) {
|
||||||
return _buildEmptyState(
|
return EmptyStateWidget(
|
||||||
emptyI18n.no_recommended_shifts,
|
message: emptyI18n.no_recommended_shifts,
|
||||||
null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
@@ -217,8 +225,8 @@ class _WorkerHomePageState extends State<WorkerHomePage> {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
itemBuilder: (context, index) => Padding(
|
itemBuilder: (context, index) => Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
child: _buildRecommendedCard(
|
child: RecommendedShiftCard(
|
||||||
state.recommendedShifts[index],
|
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