feat(staff): integrate staff_home feature
- Created staff_main package with module, bloc, and pages - Integrated staff_home into staff_main - Updated route constants to use /worker-main - Fixed intl version conflict
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
avoid_print: true
|
||||||
|
prefer_single_quotes: true
|
||||||
|
always_use_package_imports: true
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
|
import 'package:staff_home/src/data/services/mock_service.dart';
|
||||||
|
|
||||||
|
class HomeRepositoryImpl implements HomeRepository {
|
||||||
|
final MockService _service;
|
||||||
|
|
||||||
|
HomeRepositoryImpl(this._service);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Shift>> getTodayShifts() => _service.getTodayShifts();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Shift>> getTomorrowShifts() => _service.getTomorrowShifts();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Shift>> getRecommendedShifts() => _service.getRecommendedShifts();
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
|
||||||
|
class MockService {
|
||||||
|
static final Shift _sampleShift1 = Shift(
|
||||||
|
id: '1',
|
||||||
|
title: 'Line Cook',
|
||||||
|
clientName: 'The Burger Joint',
|
||||||
|
hourlyRate: 22.50,
|
||||||
|
location: 'Downtown, NY',
|
||||||
|
locationAddress: '123 Main St, New York, NY 10001',
|
||||||
|
date: DateTime.now().toIso8601String(),
|
||||||
|
startTime: '16:00',
|
||||||
|
endTime: '22:00',
|
||||||
|
createdDate: DateTime.now()
|
||||||
|
.subtract(const Duration(hours: 2))
|
||||||
|
.toIso8601String(),
|
||||||
|
tipsAvailable: true,
|
||||||
|
mealProvided: true,
|
||||||
|
managers: [ShiftManager(name: 'John Doe', phone: '+1 555 0101')],
|
||||||
|
description: 'Help with dinner service. Must be experienced with grill.',
|
||||||
|
);
|
||||||
|
|
||||||
|
static final Shift _sampleShift2 = Shift(
|
||||||
|
id: '2',
|
||||||
|
title: 'Dishwasher',
|
||||||
|
clientName: 'Pasta Place',
|
||||||
|
hourlyRate: 18.00,
|
||||||
|
location: 'Brooklyn, NY',
|
||||||
|
locationAddress: '456 Bedford Ave, Brooklyn, NY 11211',
|
||||||
|
date: DateTime.now().add(const Duration(days: 1)).toIso8601String(),
|
||||||
|
startTime: '18:00',
|
||||||
|
endTime: '23:00',
|
||||||
|
createdDate: DateTime.now()
|
||||||
|
.subtract(const Duration(hours: 5))
|
||||||
|
.toIso8601String(),
|
||||||
|
tipsAvailable: false,
|
||||||
|
mealProvided: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
static final Shift _sampleShift3 = Shift(
|
||||||
|
id: '3',
|
||||||
|
title: 'Bartender',
|
||||||
|
clientName: 'Rooftop Bar',
|
||||||
|
hourlyRate: 25.00,
|
||||||
|
location: 'Manhattan, NY',
|
||||||
|
locationAddress: '789 5th Ave, New York, NY 10022',
|
||||||
|
date: DateTime.now().add(const Duration(days: 2)).toIso8601String(),
|
||||||
|
startTime: '19:00',
|
||||||
|
endTime: '02:00',
|
||||||
|
createdDate: DateTime.now()
|
||||||
|
.subtract(const Duration(hours: 1))
|
||||||
|
.toIso8601String(),
|
||||||
|
tipsAvailable: true,
|
||||||
|
parkingAvailable: true,
|
||||||
|
description: 'High volume bar. Mixology experience required.',
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<List<Shift>> getTodayShifts() async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
return [_sampleShift1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Shift>> getTomorrowShifts() async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
return [_sampleShift2];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Shift>> getRecommendedShifts() async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
return [_sampleShift3, _sampleShift1, _sampleShift2];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createWorkerProfile(Map<String, dynamic> data) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final mockService = MockService();
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
class Shift {
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String clientName;
|
||||||
|
final String? logoUrl;
|
||||||
|
final double hourlyRate;
|
||||||
|
final String location;
|
||||||
|
final String? locationAddress;
|
||||||
|
final String date;
|
||||||
|
final String startTime;
|
||||||
|
final String endTime;
|
||||||
|
final String createdDate;
|
||||||
|
final bool? tipsAvailable;
|
||||||
|
final bool? travelTime;
|
||||||
|
final bool? mealProvided;
|
||||||
|
final bool? parkingAvailable;
|
||||||
|
final bool? gasCompensation;
|
||||||
|
final String? description;
|
||||||
|
final String? instructions;
|
||||||
|
final List<ShiftManager>? managers;
|
||||||
|
final double? latitude;
|
||||||
|
final double? longitude;
|
||||||
|
final String? status;
|
||||||
|
final int? durationDays;
|
||||||
|
|
||||||
|
Shift({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.clientName,
|
||||||
|
this.logoUrl,
|
||||||
|
required this.hourlyRate,
|
||||||
|
required this.location,
|
||||||
|
this.locationAddress,
|
||||||
|
required this.date,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
required this.createdDate,
|
||||||
|
this.tipsAvailable,
|
||||||
|
this.travelTime,
|
||||||
|
this.mealProvided,
|
||||||
|
this.parkingAvailable,
|
||||||
|
this.gasCompensation,
|
||||||
|
this.description,
|
||||||
|
this.instructions,
|
||||||
|
this.managers,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
this.status,
|
||||||
|
this.durationDays,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShiftManager {
|
||||||
|
final String name;
|
||||||
|
final String phone;
|
||||||
|
final String? avatar;
|
||||||
|
|
||||||
|
ShiftManager({required this.name, required this.phone, this.avatar});
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
|
||||||
|
abstract class HomeRepository {
|
||||||
|
Future<List<Shift>> getTodayShifts();
|
||||||
|
Future<List<Shift>> getTomorrowShifts();
|
||||||
|
Future<List<Shift>> getRecommendedShifts();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
|
|
||||||
|
class GetHomeShifts {
|
||||||
|
final HomeRepository repository;
|
||||||
|
|
||||||
|
GetHomeShifts(this.repository);
|
||||||
|
|
||||||
|
Future<HomeShifts> call() async {
|
||||||
|
final today = await repository.getTodayShifts();
|
||||||
|
final tomorrow = await repository.getTomorrowShifts();
|
||||||
|
final recommended = await repository.getRecommendedShifts();
|
||||||
|
return HomeShifts(
|
||||||
|
today: today,
|
||||||
|
tomorrow: tomorrow,
|
||||||
|
recommended: recommended,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomeShifts {
|
||||||
|
final List<Shift> today;
|
||||||
|
final List<Shift> tomorrow;
|
||||||
|
final List<Shift> recommended;
|
||||||
|
|
||||||
|
HomeShifts({
|
||||||
|
required this.today,
|
||||||
|
required this.tomorrow,
|
||||||
|
required this.recommended,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
import 'package:staff_home/src/domain/usecases/get_home_shifts.dart';
|
||||||
|
import 'package:staff_home/src/domain/repositories/home_repository.dart';
|
||||||
|
|
||||||
|
part 'home_state.dart';
|
||||||
|
|
||||||
|
/// Simple Cubit to manage home page state (shifts + loading/error).
|
||||||
|
class HomeCubit extends Cubit<HomeState> {
|
||||||
|
final GetHomeShifts _getHomeShifts;
|
||||||
|
|
||||||
|
HomeCubit(HomeRepository repository)
|
||||||
|
: _getHomeShifts = GetHomeShifts(repository),
|
||||||
|
super(const HomeState.initial());
|
||||||
|
|
||||||
|
Future<void> loadShifts() async {
|
||||||
|
emit(state.copyWith(status: HomeStatus.loading));
|
||||||
|
try {
|
||||||
|
final result = await _getHomeShifts.call();
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: HomeStatus.loaded,
|
||||||
|
todayShifts: result.today,
|
||||||
|
tomorrowShifts: result.tomorrow,
|
||||||
|
recommendedShifts: result.recommended,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(status: HomeStatus.error, errorMessage: e.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
part of 'home_cubit.dart';
|
||||||
|
|
||||||
|
enum HomeStatus { initial, loading, loaded, error }
|
||||||
|
|
||||||
|
class HomeState extends Equatable {
|
||||||
|
final HomeStatus status;
|
||||||
|
final List<Shift> todayShifts;
|
||||||
|
final List<Shift> tomorrowShifts;
|
||||||
|
final List<Shift> recommendedShifts;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const HomeState({
|
||||||
|
required this.status,
|
||||||
|
this.todayShifts = const [],
|
||||||
|
this.tomorrowShifts = const [],
|
||||||
|
this.recommendedShifts = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const HomeState.initial() : this(status: HomeStatus.initial);
|
||||||
|
|
||||||
|
HomeState copyWith({
|
||||||
|
HomeStatus? status,
|
||||||
|
List<Shift>? todayShifts,
|
||||||
|
List<Shift>? tomorrowShifts,
|
||||||
|
List<Shift>? recommendedShifts,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return HomeState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
todayShifts: todayShifts ?? this.todayShifts,
|
||||||
|
tomorrowShifts: tomorrowShifts ?? this.tomorrowShifts,
|
||||||
|
recommendedShifts: recommendedShifts ?? this.recommendedShifts,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
status,
|
||||||
|
todayShifts,
|
||||||
|
tomorrowShifts,
|
||||||
|
recommendedShifts,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
|
||||||
|
/// Extension on [IModularNavigator] providing typed navigation helpers
|
||||||
|
/// for the Staff Home feature (worker home screen).
|
||||||
|
///
|
||||||
|
/// Keep routes as small wrappers around `pushNamed` / `navigate` so callers
|
||||||
|
/// don't need to rely on literal paths throughout the codebase.
|
||||||
|
extension HomeNavigator on IModularNavigator {
|
||||||
|
/// Navigates to the worker profile page.
|
||||||
|
void pushWorkerProfile() {
|
||||||
|
pushNamed('/worker-profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigates to the availability page.
|
||||||
|
void pushAvailability() {
|
||||||
|
pushNamed('/availability');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigates to the messages page.
|
||||||
|
void pushMessages() {
|
||||||
|
pushNamed('/messages');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigates to the payments page.
|
||||||
|
void pushPayments() {
|
||||||
|
pushNamed('/payments');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Navigates to the shifts listing.
|
||||||
|
/// Optionally provide a [tab] query param (e.g. `find`).
|
||||||
|
void pushShifts({String? tab}) {
|
||||||
|
if (tab == null) {
|
||||||
|
pushNamed('/shifts');
|
||||||
|
} else {
|
||||||
|
pushNamed('/shifts?tab=$tab');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,850 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
|
||||||
|
import 'package:staff_home/src/theme.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/data/services/mock_service.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/data/repositories/home_repository_impl.dart';
|
||||||
|
|
||||||
|
class WorkerHomePage extends ConsumerStatefulWidget {
|
||||||
|
const WorkerHomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<WorkerHomePage> createState() => _WorkerHomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WorkerHomePageState extends ConsumerState<WorkerHomePage> {
|
||||||
|
bool _autoMatchEnabled = false;
|
||||||
|
final bool _isProfileComplete = false; // Added for mock profile completion
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home;
|
||||||
|
final headerI18n = i18n.header;
|
||||||
|
final bannersI18n = i18n.banners;
|
||||||
|
final quickI18n = i18n.quick_actions;
|
||||||
|
final sectionsI18n = i18n.sections;
|
||||||
|
final emptyI18n = i18n.empty_states;
|
||||||
|
final pendingI18n = i18n.pending_payment;
|
||||||
|
final recI18n = i18n.recommended_card;
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => HomeCubit(
|
||||||
|
// provide repository implementation backed by mock service for now
|
||||||
|
// later this should be wired from a DI container
|
||||||
|
HomeRepositoryImpl(mockService),
|
||||||
|
)..loadShifts(),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: AppColors.krowBackground,
|
||||||
|
body: SafeArea(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.only(bottom: 100),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildPlaceholderBanner(
|
||||||
|
bannersI18n.availability_title,
|
||||||
|
bannersI18n.availability_subtitle,
|
||||||
|
Colors.orange[50]!,
|
||||||
|
Colors.orange,
|
||||||
|
onTap: () => Modular.to.pushAvailability(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Auto Match Toggle
|
||||||
|
AutoMatchToggle(
|
||||||
|
enabled: _autoMatchEnabled,
|
||||||
|
onToggle: (val) =>
|
||||||
|
setState(() => _autoMatchEnabled = val),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Quick Actions
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildQuickAction(
|
||||||
|
context,
|
||||||
|
LucideIcons.search,
|
||||||
|
quickI18n.find_shifts,
|
||||||
|
() => Modular.to.pushShifts(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildQuickAction(
|
||||||
|
context,
|
||||||
|
LucideIcons.calendar,
|
||||||
|
quickI18n.availability,
|
||||||
|
() => Modular.to.pushAvailability(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildQuickAction(
|
||||||
|
context,
|
||||||
|
LucideIcons.messageSquare,
|
||||||
|
quickI18n.messages,
|
||||||
|
() => Modular.to.pushMessages(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildQuickAction(
|
||||||
|
context,
|
||||||
|
LucideIcons.dollarSign,
|
||||||
|
quickI18n.earnings,
|
||||||
|
() => Modular.to.pushPayments(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Today's Shifts
|
||||||
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final shifts = state.todayShifts;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildSectionHeader(
|
||||||
|
sectionsI18n.todays_shift,
|
||||||
|
shifts.isNotEmpty
|
||||||
|
? sectionsI18n.scheduled_count.replaceAll(
|
||||||
|
r'$count',
|
||||||
|
'${shifts.length}',
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (state.status == HomeStatus.loading)
|
||||||
|
const Center(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (shifts.isEmpty)
|
||||||
|
_buildEmptyState(
|
||||||
|
emptyI18n.no_shifts_today,
|
||||||
|
emptyI18n.find_shifts_cta,
|
||||||
|
() => Modular.to.pushShifts(tab: 'find'),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Column(
|
||||||
|
children: shifts
|
||||||
|
.map(
|
||||||
|
(shift) => ShiftCard(
|
||||||
|
shift: shift,
|
||||||
|
compact: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Tomorrow's Shifts
|
||||||
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final shifts = state.tomorrowShifts;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_buildSectionHeader(sectionsI18n.tomorrow, null),
|
||||||
|
if (shifts.isEmpty)
|
||||||
|
_buildEmptyState(
|
||||||
|
emptyI18n.no_shifts_tomorrow,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Column(
|
||||||
|
children: shifts
|
||||||
|
.map(
|
||||||
|
(shift) => ShiftCard(
|
||||||
|
shift: shift,
|
||||||
|
compact: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Pending Payment Card
|
||||||
|
_buildPendingPaymentCard(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Recommended Shifts
|
||||||
|
_buildSectionHeader(
|
||||||
|
sectionsI18n.recommended_for_you,
|
||||||
|
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 SizedBox(
|
||||||
|
height: 160,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: state.recommendedShifts.length,
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
itemBuilder: (context, index) => Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12),
|
||||||
|
child: _buildRecommendedCard(
|
||||||
|
state.recommendedShifts[index],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
const BenefitsWidget(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
const ImproveYourselfWidget(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
const MoreWaysToUseKrowWidget(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
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() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => context.go('/payments'),
|
||||||
|
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 duration = 8;
|
||||||
|
final totalPay = duration * shift.hourlyRate;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
recI18n.applied_for.replaceAll(r'$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
|
||||||
|
.replaceAll(r'$start', shift.startTime)
|
||||||
|
.replaceAll(r'$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,497 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import 'package:staff_home/src/domain/models/shift.dart';
|
||||||
|
import 'package:staff_home/src/theme.dart';
|
||||||
|
|
||||||
|
class ShiftCard extends StatefulWidget {
|
||||||
|
final Shift shift;
|
||||||
|
final VoidCallback? onApply;
|
||||||
|
final VoidCallback? onDecline;
|
||||||
|
final bool compact;
|
||||||
|
final bool disableTapNavigation; // Added property
|
||||||
|
|
||||||
|
const ShiftCard({
|
||||||
|
super.key,
|
||||||
|
required this.shift,
|
||||||
|
this.onApply,
|
||||||
|
this.onDecline,
|
||||||
|
this.compact = false,
|
||||||
|
this.disableTapNavigation = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShiftCard> createState() => _ShiftCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShiftCardState extends State<ShiftCard> {
|
||||||
|
bool isExpanded = false;
|
||||||
|
|
||||||
|
String _formatTime(String time) {
|
||||||
|
if (time.isEmpty) return '';
|
||||||
|
try {
|
||||||
|
final parts = time.split(':');
|
||||||
|
final hour = int.parse(parts[0]);
|
||||||
|
final minute = int.parse(parts[1]);
|
||||||
|
final dt = DateTime(2022, 1, 1, hour, minute);
|
||||||
|
return DateFormat('h:mma').format(dt).toLowerCase();
|
||||||
|
} catch (e) {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(String dateStr) {
|
||||||
|
if (dateStr.isEmpty) return '';
|
||||||
|
try {
|
||||||
|
final date = DateTime.parse(dateStr);
|
||||||
|
return DateFormat('MMMM d').format(date);
|
||||||
|
} catch (e) {
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getTimeAgo(String dateStr) {
|
||||||
|
if (dateStr.isEmpty) return '';
|
||||||
|
try {
|
||||||
|
final date = DateTime.parse(dateStr);
|
||||||
|
final diff = DateTime.now().difference(date);
|
||||||
|
if (diff.inHours < 1) return 'Just now';
|
||||||
|
if (diff.inHours < 24) return 'Pending ${diff.inHours}h ago';
|
||||||
|
return 'Pending ${diff.inDays}d ago';
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _calculateDuration() {
|
||||||
|
if (widget.shift.startTime.isEmpty || widget.shift.endTime.isEmpty) {
|
||||||
|
return {'hours': 0, 'breakTime': '1 hour'};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final startParts = widget.shift.startTime
|
||||||
|
.split(':')
|
||||||
|
.map(int.parse)
|
||||||
|
.toList();
|
||||||
|
final endParts = widget.shift.endTime.split(':').map(int.parse).toList();
|
||||||
|
double hours =
|
||||||
|
(endParts[0] - startParts[0]) + (endParts[1] - startParts[1]) / 60;
|
||||||
|
if (hours < 0) hours += 24;
|
||||||
|
return {'hours': hours.round(), 'breakTime': '1 hour'};
|
||||||
|
} catch (e) {
|
||||||
|
return {'hours': 0, 'breakTime': '1 hour'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.compact) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: widget.disableTapNavigation
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
setState(() => isExpanded = !isExpanded);
|
||||||
|
GoRouter.of(context).push(
|
||||||
|
'/shift-details/${widget.shift.id}',
|
||||||
|
extra: widget.shift,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
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.05),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: AppColors.krowBorder),
|
||||||
|
),
|
||||||
|
child: widget.shift.logoUrl != null
|
||||||
|
? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Image.network(
|
||||||
|
widget.shift.logoUrl!,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
LucideIcons.building2,
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
widget.shift.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: '\$${widget.shift.hourlyRate}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
color: AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
children: const [
|
||||||
|
TextSpan(
|
||||||
|
text: '/h',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.shift.clientName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${_formatTime(widget.shift.startTime)} • ${widget.shift.location}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: AppColors.krowBorder),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: AppColors.krowBorder),
|
||||||
|
),
|
||||||
|
child: widget.shift.logoUrl != null
|
||||||
|
? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Image.network(
|
||||||
|
widget.shift.logoUrl!,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
LucideIcons.building2,
|
||||||
|
size: 28,
|
||||||
|
color: AppColors.krowBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.krowBlue,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Assigned ${_getTimeAgo(widget.shift.createdDate).replaceAll('Pending ', '')}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Title and Rate
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.shift.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.shift.clientName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: '\$${widget.shift.hourlyRate}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
color: AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
children: const [
|
||||||
|
TextSpan(
|
||||||
|
text: '/h',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Location and Date
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.mapPin,
|
||||||
|
size: 16,
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.shift.location,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.calendar,
|
||||||
|
size: 16,
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
_buildTag(
|
||||||
|
LucideIcons.zap,
|
||||||
|
'Immediate start',
|
||||||
|
AppColors.krowYellow.withValues(alpha: 0.3),
|
||||||
|
AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
_buildTag(
|
||||||
|
LucideIcons.timer,
|
||||||
|
'No experience',
|
||||||
|
const Color(0xFFFEE2E2),
|
||||||
|
const Color(0xFFDC2626),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
if (!widget.compact)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: widget.onApply,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppColors.krowCharcoal,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Accept shift',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 48,
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: widget.onDecline,
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFFEF4444),
|
||||||
|
side: const BorderSide(color: Color(0xFFFCA5A5)),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Decline shift',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTag(IconData icon, String label, Color bg, Color text) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bg,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 14, color: text),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: text,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailRow(IconData icon, String label, bool? value) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(color: AppColors.krowBorder)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 16, color: AppColors.krowMuted),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppColors.krowMuted,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value == true ? 'Yes' : 'No',
|
||||||
|
style: TextStyle(
|
||||||
|
color: value == true
|
||||||
|
? const Color(0xFF10B981)
|
||||||
|
: AppColors.krowMuted,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
|
||||||
|
class AutoMatchToggle extends StatefulWidget {
|
||||||
|
final bool enabled;
|
||||||
|
final ValueChanged<bool> onToggle;
|
||||||
|
|
||||||
|
const AutoMatchToggle({
|
||||||
|
super.key,
|
||||||
|
required this.enabled,
|
||||||
|
required this.onToggle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AutoMatchToggle> createState() => _AutoMatchToggleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AutoMatchToggleState extends State<AutoMatchToggle>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.auto_match;
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
gradient: widget.enabled
|
||||||
|
? const LinearGradient(
|
||||||
|
colors: [Color(0xFF0032A0), Color(0xFF0047CC)],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
color: widget.enabled ? null : Colors.white,
|
||||||
|
border: widget.enabled ? null : Border.all(color: Colors.grey.shade200),
|
||||||
|
boxShadow: widget.enabled
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xFF0032A0).withOpacity(0.3),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: widget.enabled
|
||||||
|
? Colors.white.withOpacity(0.2)
|
||||||
|
: const Color(0xFF0032A0).withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LucideIcons.zap,
|
||||||
|
color: widget.enabled
|
||||||
|
? Colors.white
|
||||||
|
: const Color(0xFF0032A0),
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: widget.enabled
|
||||||
|
? Colors.white
|
||||||
|
: const Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.enabled ? i18n.finding_shifts : i18n.get_matched,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: widget.enabled
|
||||||
|
? const Color(0xFFF8E08E)
|
||||||
|
: Colors.grey.shade500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: widget.enabled,
|
||||||
|
onChanged: widget.onToggle,
|
||||||
|
activeThumbColor: Colors.white,
|
||||||
|
activeTrackColor: Colors.white.withOpacity(0.3),
|
||||||
|
inactiveThumbColor: Colors.white,
|
||||||
|
inactiveTrackColor: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: widget.enabled
|
||||||
|
? Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
height: 1,
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
i18n.matching_based_on,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFF8E08E),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
_buildChip(LucideIcons.mapPin, i18n.chips.location),
|
||||||
|
_buildChip(
|
||||||
|
LucideIcons.clock,
|
||||||
|
i18n.chips.availability,
|
||||||
|
),
|
||||||
|
_buildChip(LucideIcons.briefcase, i18n.chips.skills),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChip(IconData icon, String label) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 12, color: Colors.white),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lucide_icons/lucide_icons.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
|
||||||
|
class BenefitsWidget extends StatelessWidget {
|
||||||
|
const BenefitsWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.benefits;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => context.push('/benefits'),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.view_all,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF0032A0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
LucideIcons.chevronRight,
|
||||||
|
size: 16,
|
||||||
|
color: Color(0xFF0032A0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_BenefitItem(
|
||||||
|
label: i18n.items.sick_days,
|
||||||
|
current: 10,
|
||||||
|
total: 40,
|
||||||
|
color: const Color(0xFF0A39DF),
|
||||||
|
),
|
||||||
|
_BenefitItem(
|
||||||
|
label: i18n.items.vacation,
|
||||||
|
current: 40,
|
||||||
|
total: 40,
|
||||||
|
color: const Color(0xFF0A39DF),
|
||||||
|
),
|
||||||
|
_BenefitItem(
|
||||||
|
label: i18n.items.holidays,
|
||||||
|
current: 24,
|
||||||
|
total: 24,
|
||||||
|
color: const Color(0xFF0A39DF),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BenefitItem extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final double current;
|
||||||
|
final double total;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
const _BenefitItem({
|
||||||
|
required this.label,
|
||||||
|
required this.current,
|
||||||
|
required this.total,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.benefits;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: _CircularProgressPainter(
|
||||||
|
progress: current / total,
|
||||||
|
color: color,
|
||||||
|
backgroundColor: const Color(0xFFE5E7EB),
|
||||||
|
strokeWidth: 4,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${current.toInt()}/${total.toInt()}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xFF1E293B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
i18n.hours_label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 8,
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF475569),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CircularProgressPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
final Color color;
|
||||||
|
final Color backgroundColor;
|
||||||
|
final double strokeWidth;
|
||||||
|
|
||||||
|
_CircularProgressPainter({
|
||||||
|
required this.progress,
|
||||||
|
required this.color,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.strokeWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
|
final radius = (size.width - strokeWidth) / 2;
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = backgroundColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth;
|
||||||
|
canvas.drawCircle(center, radius, backgroundPaint);
|
||||||
|
|
||||||
|
final progressPaint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = strokeWidth
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
final sweepAngle = 2 * math.pi * progress;
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
|
-math.pi / 2,
|
||||||
|
sweepAngle,
|
||||||
|
false,
|
||||||
|
progressPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
|
||||||
|
class ImproveYourselfWidget extends StatelessWidget {
|
||||||
|
const ImproveYourselfWidget({super.key});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.improve;
|
||||||
|
final items = [
|
||||||
|
{
|
||||||
|
'id': 'training',
|
||||||
|
'title': i18n.items.training.title,
|
||||||
|
'description': i18n.items.training.description,
|
||||||
|
'image':
|
||||||
|
'https://images.unsplash.com/photo-1524995997946-a1c2e315a42f?w=400&h=300&fit=crop',
|
||||||
|
'page': i18n.items.training.page,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'podcast',
|
||||||
|
'title': i18n.items.podcast.title,
|
||||||
|
'description': i18n.items.podcast.description,
|
||||||
|
'image':
|
||||||
|
'https://images.unsplash.com/photo-1478737270239-2f02b77fc618?w=400&h=300&fit=crop',
|
||||||
|
'page': i18n.items.podcast.page,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
child: Row(
|
||||||
|
children: items.map((item) => _buildCard(context, item)).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCard(BuildContext context, Map<String, String> item) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => context.push(item['page']!),
|
||||||
|
child: Container(
|
||||||
|
width: 160,
|
||||||
|
margin: const EdgeInsets.only(right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 96,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Image.network(
|
||||||
|
item['image']!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) => Container(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.image_not_supported,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item['title']!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
item['description']!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF64748B),
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
|
||||||
|
class MoreWaysToUseKrowWidget extends StatelessWidget {
|
||||||
|
const MoreWaysToUseKrowWidget({super.key});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final i18n = t.staff.home.more_ways;
|
||||||
|
final items = [
|
||||||
|
{
|
||||||
|
'id': 'benefits',
|
||||||
|
'title': i18n.items.benefits.title,
|
||||||
|
'image':
|
||||||
|
'https://images.unsplash.com/photo-1481627834876-b7833e8f5570?w=400&h=300&fit=crop',
|
||||||
|
'page': i18n.items.benefits.page,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'refer',
|
||||||
|
'title': i18n.items.refer.title,
|
||||||
|
'image':
|
||||||
|
'https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&h=300&fit=crop',
|
||||||
|
'page': i18n.items.refer.page,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
i18n.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
child: Row(
|
||||||
|
children: items.map((item) => _buildCard(context, item)).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCard(BuildContext context, Map<String, String> item) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => context.push(item['page']!),
|
||||||
|
child: Container(
|
||||||
|
width: 160,
|
||||||
|
margin: const EdgeInsets.only(right: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: Colors.grey.shade100),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 96,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Image.network(
|
||||||
|
item['image']!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, error, stackTrace) => Container(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.image_not_supported,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Text(
|
||||||
|
item['title']!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF0F172A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
|
||||||
|
import 'package:staff_home/src/presentation/pages/worker_home_page.dart';
|
||||||
|
|
||||||
|
class StaffHomeModule extends Module {
|
||||||
|
@override
|
||||||
|
void binds(Injector i) {
|
||||||
|
i.addSingleton(HomeCubit.new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void routes(RouteManager r) {
|
||||||
|
r.child('/', child: (BuildContext context) => const WorkerHomePage());
|
||||||
|
}
|
||||||
|
}
|
||||||
51
apps/mobile/packages/features/staff/home/lib/src/theme.dart
Normal file
51
apps/mobile/packages/features/staff/home/lib/src/theme.dart
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
|
class AppColors {
|
||||||
|
static const Color krowBlue = Color(0xFF0A39DF);
|
||||||
|
static const Color krowYellow = Color(0xFFFFED4A);
|
||||||
|
static const Color krowCharcoal = Color(0xFF121826);
|
||||||
|
static const Color krowMuted = Color(0xFF6A7382);
|
||||||
|
static const Color krowBorder = Color(0xFFE3E6E9);
|
||||||
|
static const Color krowBackground = Color(0xFFFAFBFC);
|
||||||
|
|
||||||
|
static const Color white = Colors.white;
|
||||||
|
static const Color black = Colors.black;
|
||||||
|
|
||||||
|
// helpers used by prototype (withValues extension in prototype); keep simple aliases
|
||||||
|
static Color withAlpha(Color c, double alpha) => c.withOpacity(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppTheme {
|
||||||
|
static ThemeData get lightTheme {
|
||||||
|
return ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
scaffoldBackgroundColor: AppColors.krowBackground,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: AppColors.krowBlue,
|
||||||
|
primary: AppColors.krowBlue,
|
||||||
|
secondary: AppColors.krowYellow,
|
||||||
|
surface: AppColors.white,
|
||||||
|
background: AppColors.krowBackground,
|
||||||
|
),
|
||||||
|
textTheme: GoogleFonts.instrumentSansTextTheme().apply(
|
||||||
|
bodyColor: AppColors.krowCharcoal,
|
||||||
|
displayColor: AppColors.krowCharcoal,
|
||||||
|
),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: AppColors.krowBackground,
|
||||||
|
elevation: 0,
|
||||||
|
iconTheme: IconThemeData(color: AppColors.krowCharcoal),
|
||||||
|
titleTextStyle: TextStyle(
|
||||||
|
color: AppColors.krowCharcoal,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ColorWithValues on Color {
|
||||||
|
Color withValues({double alpha = 1.0}) => withOpacity(alpha);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/staff_home_module.dart';
|
||||||
38
apps/mobile/packages/features/staff/home/pubspec.yaml
Normal file
38
apps/mobile/packages/features/staff/home/pubspec.yaml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: staff_home
|
||||||
|
description: Home feature for the staff application.
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
resolution: workspace
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.10.0 <4.0.0'
|
||||||
|
flutter: ">=3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_bloc: ^8.1.0
|
||||||
|
flutter_modular: ^6.3.0
|
||||||
|
equatable: ^2.0.5
|
||||||
|
lucide_icons: ^0.257.0
|
||||||
|
intl: ^0.20.0
|
||||||
|
|
||||||
|
# Architecture Packages
|
||||||
|
design_system:
|
||||||
|
path: ../../../design_system
|
||||||
|
core_localization:
|
||||||
|
path: ../../../core_localization
|
||||||
|
krow_core:
|
||||||
|
path: ../../../core
|
||||||
|
krow_domain:
|
||||||
|
path: ../../../domain
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
bloc_test: ^9.1.0
|
||||||
|
mocktail: ^1.0.0
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:staff_home/staff_home.dart';
|
||||||
|
|
||||||
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
|
import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart';
|
||||||
import 'package:staff_main/src/presentation/constants/staff_main_routes.dart';
|
import 'package:staff_main/src/presentation/constants/staff_main_routes.dart';
|
||||||
@@ -28,9 +29,9 @@ class StaffMainModule extends Module {
|
|||||||
child: (BuildContext context) =>
|
child: (BuildContext context) =>
|
||||||
const PlaceholderPage(title: 'Payments'),
|
const PlaceholderPage(title: 'Payments'),
|
||||||
),
|
),
|
||||||
ChildRoute<dynamic>(
|
ModuleRoute<dynamic>(
|
||||||
StaffMainRoutes.home,
|
StaffMainRoutes.home,
|
||||||
child: (BuildContext context) => const PlaceholderPage(title: 'Home'),
|
module: StaffHomeModule(),
|
||||||
),
|
),
|
||||||
ChildRoute<dynamic>(
|
ChildRoute<dynamic>(
|
||||||
StaffMainRoutes.clockIn,
|
StaffMainRoutes.clockIn,
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ dependencies:
|
|||||||
core_localization:
|
core_localization:
|
||||||
path: ../../../core_localization
|
path: ../../../core_localization
|
||||||
|
|
||||||
# Features (Commented out until they are ready)
|
# Features
|
||||||
# staff_home:
|
staff_home:
|
||||||
# path: ../home
|
path: ../home
|
||||||
# staff_shifts:
|
# staff_shifts:
|
||||||
# path: ../shifts
|
# path: ../shifts
|
||||||
# staff_payments:
|
# staff_payments:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ workspace:
|
|||||||
- packages/data_connect
|
- packages/data_connect
|
||||||
- packages/core_localization
|
- packages/core_localization
|
||||||
- packages/features/staff/authentication
|
- packages/features/staff/authentication
|
||||||
|
- packages/features/staff/home
|
||||||
- packages/features/staff/staff_main
|
- packages/features/staff/staff_main
|
||||||
- packages/features/client/authentication
|
- packages/features/client/authentication
|
||||||
- packages/features/client/home
|
- packages/features/client/home
|
||||||
|
|||||||
Reference in New Issue
Block a user