feat: Enhance shift application process with instant booking option and implement shift booking and decline dialogs
This commit is contained in:
@@ -223,7 +223,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> applyForShift(String shiftId) async {
|
Future<void> applyForShift(String shiftId, {bool isInstantBook = false}) async {
|
||||||
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
||||||
if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift');
|
if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift');
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
roleId: role.id,
|
roleId: role.id,
|
||||||
status: dc.ApplicationStatus.PENDING,
|
status: isInstantBook ? dc.ApplicationStatus.ACCEPTED : dc.ApplicationStatus.PENDING,
|
||||||
origin: dc.ApplicationOrigin.STAFF,
|
origin: dc.ApplicationOrigin.STAFF,
|
||||||
).execute();
|
).execute();
|
||||||
}
|
}
|
||||||
@@ -273,6 +273,22 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appId == null || roleId == null) {
|
if (appId == null || roleId == null) {
|
||||||
|
// If we are rejecting and can't find an application, create one as rejected (declining an available shift)
|
||||||
|
if (newStatus == dc.ApplicationStatus.REJECTED) {
|
||||||
|
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
|
||||||
|
if (rolesResult.data.shiftRoles.isNotEmpty) {
|
||||||
|
final role = rolesResult.data.shiftRoles.first;
|
||||||
|
final staffId = await _getStaffId();
|
||||||
|
await _dataConnect.createApplication(
|
||||||
|
shiftId: shiftId,
|
||||||
|
staffId: staffId,
|
||||||
|
roleId: role.id,
|
||||||
|
status: dc.ApplicationStatus.REJECTED,
|
||||||
|
origin: dc.ApplicationOrigin.STAFF,
|
||||||
|
).execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
throw Exception("Application not found for shift $shiftId");
|
throw Exception("Application not found for shift $shiftId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ abstract interface class ShiftsRepositoryInterface {
|
|||||||
Future<Shift?> getShiftDetails(String shiftId);
|
Future<Shift?> getShiftDetails(String shiftId);
|
||||||
|
|
||||||
/// Applies for a specific open shift.
|
/// Applies for a specific open shift.
|
||||||
Future<void> applyForShift(String shiftId);
|
///
|
||||||
|
/// [isInstantBook] determines if the application should be immediately accepted.
|
||||||
|
Future<void> applyForShift(String shiftId, {bool isInstantBook = false});
|
||||||
|
|
||||||
/// Accepts a pending shift assignment.
|
/// Accepts a pending shift assignment.
|
||||||
Future<void> acceptShift(String shiftId);
|
Future<void> acceptShift(String shiftId);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import '../repositories/shifts_repository_interface.dart';
|
||||||
|
|
||||||
|
class ApplyForShiftUseCase {
|
||||||
|
final ShiftsRepositoryInterface repository;
|
||||||
|
|
||||||
|
ApplyForShiftUseCase(this.repository);
|
||||||
|
|
||||||
|
Future<void> call(String shiftId, {bool isInstantBook = false}) async {
|
||||||
|
return repository.applyForShift(shiftId, isInstantBook: isInstantBook);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
|
|||||||
import '../../../domain/usecases/get_history_shifts_usecase.dart';
|
import '../../../domain/usecases/get_history_shifts_usecase.dart';
|
||||||
import '../../../domain/usecases/accept_shift_usecase.dart';
|
import '../../../domain/usecases/accept_shift_usecase.dart';
|
||||||
import '../../../domain/usecases/decline_shift_usecase.dart';
|
import '../../../domain/usecases/decline_shift_usecase.dart';
|
||||||
|
import '../../../domain/usecases/apply_for_shift_usecase.dart';
|
||||||
|
|
||||||
part 'shifts_event.dart';
|
part 'shifts_event.dart';
|
||||||
part 'shifts_state.dart';
|
part 'shifts_state.dart';
|
||||||
@@ -23,6 +24,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
final GetHistoryShiftsUseCase getHistoryShifts;
|
final GetHistoryShiftsUseCase getHistoryShifts;
|
||||||
final AcceptShiftUseCase acceptShift;
|
final AcceptShiftUseCase acceptShift;
|
||||||
final DeclineShiftUseCase declineShift;
|
final DeclineShiftUseCase declineShift;
|
||||||
|
final ApplyForShiftUseCase applyForShift;
|
||||||
|
|
||||||
ShiftsBloc({
|
ShiftsBloc({
|
||||||
required this.getMyShifts,
|
required this.getMyShifts,
|
||||||
@@ -32,11 +34,13 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
required this.getHistoryShifts,
|
required this.getHistoryShifts,
|
||||||
required this.acceptShift,
|
required this.acceptShift,
|
||||||
required this.declineShift,
|
required this.declineShift,
|
||||||
|
required this.applyForShift,
|
||||||
}) : super(ShiftsInitial()) {
|
}) : super(ShiftsInitial()) {
|
||||||
on<LoadShiftsEvent>(_onLoadShifts);
|
on<LoadShiftsEvent>(_onLoadShifts);
|
||||||
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
||||||
on<AcceptShiftEvent>(_onAcceptShift);
|
on<AcceptShiftEvent>(_onAcceptShift);
|
||||||
on<DeclineShiftEvent>(_onDeclineShift);
|
on<DeclineShiftEvent>(_onDeclineShift);
|
||||||
|
on<BookShiftEvent>(_onBookShift);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadShifts(
|
Future<void> _onLoadShifts(
|
||||||
@@ -122,4 +126,16 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
// Handle error
|
// Handle error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onBookShift(
|
||||||
|
BookShiftEvent event,
|
||||||
|
Emitter<ShiftsState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await applyForShift(event.shiftId, isInstantBook: true);
|
||||||
|
add(LoadShiftsEvent()); // Reload to move from Available to My Shifts
|
||||||
|
} catch (_) {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,3 +35,11 @@ class DeclineShiftEvent extends ShiftsEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [shiftId];
|
List<Object?> get props => [shiftId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BookShiftEvent extends ShiftsEvent {
|
||||||
|
final String shiftId;
|
||||||
|
const BookShiftEvent(this.shiftId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [shiftId];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,869 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
|
||||||
import 'package:lucide_icons/lucide_icons.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
|
||||||
import 'package:staff_shifts/src/presentation/blocs/shifts/shifts_bloc.dart';
|
|
||||||
import '../../domain/usecases/get_shift_details_usecase.dart';
|
|
||||||
import '../../domain/usecases/accept_shift_usecase.dart';
|
|
||||||
import '../../domain/usecases/decline_shift_usecase.dart';
|
|
||||||
|
|
||||||
// Shim to match POC styles locally
|
|
||||||
class AppColors {
|
|
||||||
static const Color krowBlue = UiColors.primary;
|
|
||||||
static const Color krowYellow = Color(0xFFFFED4A);
|
|
||||||
static const Color krowCharcoal = UiColors.textPrimary; // 121826
|
|
||||||
static const Color krowMuted = UiColors.textSecondary; // 6A7382
|
|
||||||
static const Color krowBorder = UiColors.border; // E3E6E9
|
|
||||||
static const Color krowBackground = UiColors.background; // FAFBFC
|
|
||||||
static const Color white = Colors.white;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShiftDetailsPage extends StatefulWidget {
|
|
||||||
final String shiftId;
|
|
||||||
final Shift? shift;
|
|
||||||
|
|
||||||
const ShiftDetailsPage({super.key, required this.shiftId, this.shift});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ShiftDetailsPage> createState() => _ShiftDetailsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
|
|
||||||
late Shift _shift;
|
|
||||||
bool _isLoading = true;
|
|
||||||
bool _showDetails = true;
|
|
||||||
bool _isApplying = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadShift();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadShift() async {
|
|
||||||
if (widget.shift != null) {
|
|
||||||
_shift = widget.shift!;
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
final useCase = Modular.get<GetShiftDetailsUseCase>();
|
|
||||||
final shift = await useCase(widget.shiftId);
|
|
||||||
if (mounted) {
|
|
||||||
if (shift != null) {
|
|
||||||
setState(() {
|
|
||||||
_shift = shift;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Handle case where shift is not found
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(content: Text('Shift not found')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text('Error loading shift: $e')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _calculateHours(String start, String end) {
|
|
||||||
try {
|
|
||||||
final startParts = start.split(':').map(int.parse).toList();
|
|
||||||
final endParts = end.split(':').map(int.parse).toList();
|
|
||||||
double h = (endParts[0] - startParts[0]) + (endParts[1] - startParts[1]) / 60;
|
|
||||||
if (h < 0) h += 24;
|
|
||||||
return h;
|
|
||||||
} catch (e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (_isLoading) {
|
|
||||||
return const Scaffold(
|
|
||||||
backgroundColor: AppColors.krowBackground,
|
|
||||||
body: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final hours = _calculateHours(_shift.startTime, _shift.endTime);
|
|
||||||
final totalPay = _shift.hourlyRate * hours;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: AppColors.krowBackground,
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
elevation: 0,
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(LucideIcons.chevronLeft, color: AppColors.krowMuted),
|
|
||||||
onPressed: () => Modular.to.pop(),
|
|
||||||
),
|
|
||||||
bottom: PreferredSize(
|
|
||||||
preferredSize: const Size.fromHeight(1.0),
|
|
||||||
child: Container(color: AppColors.krowBorder, height: 1.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 120),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Pending Badge
|
|
||||||
// Status Badge
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 4,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _getStatusColor(_shift.status ?? 'open').withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
(_shift.status ?? 'open').toUpperCase(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: _getStatusColor(_shift.status ?? 'open'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Header
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 56,
|
|
||||||
height: 56,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: _shift.logoUrl != null
|
|
||||||
? ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
child: Image.network(
|
|
||||||
_shift.logoUrl!,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: Text(
|
|
||||||
_shift.clientName.isNotEmpty ? _shift.clientName[0] : 'K',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowBlue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_shift.title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'\$${_shift.hourlyRate.toStringAsFixed(0)}/h',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'(exp.total \$${totalPay.toStringAsFixed(0)})',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_shift.clientName,
|
|
||||||
style: const TextStyle(color: AppColors.krowMuted),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Additional Details Collapsible
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
InkWell(
|
|
||||||
onTap: () =>
|
|
||||||
setState(() => _showDetails = !_showDetails),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'ADDITIONAL DETAILS',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
_showDetails
|
|
||||||
? LucideIcons.chevronUp
|
|
||||||
: LucideIcons.chevronDown,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_showDetails)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_buildDetailRow('Tips', _shift.tipsAvailable == true ? 'Yes' : 'No', _shift.tipsAvailable == true),
|
|
||||||
_buildDetailRow('Travel Time', _shift.travelTime == true ? 'Yes' : 'No', _shift.travelTime == true),
|
|
||||||
_buildDetailRow('Meal Provided', _shift.mealProvided == true ? 'Yes' : 'No', _shift.mealProvided == true),
|
|
||||||
_buildDetailRow('Parking Available', _shift.parkingAvailable == true ? 'Yes' : 'No', _shift.parkingAvailable == true),
|
|
||||||
_buildDetailRow('Gas Compensation', _shift.gasCompensation == true ? 'Yes' : 'No', _shift.gasCompensation == true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Date & Duration Grid
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'START',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
_formatDate(_shift.date),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Date',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
_formatTime(_shift.startTime),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Time',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'DURATION',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'${hours.toStringAsFixed(0)} hours',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Shift duration',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
const Text(
|
|
||||||
'1 hour',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Break duration',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Location
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'LOCATION',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
_shift.location,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_shift.locationAddress,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
_shift.locationAddress,
|
|
||||||
),
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(LucideIcons.navigation, size: 14),
|
|
||||||
label: const Text('Get direction'),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: AppColors.krowCharcoal,
|
|
||||||
side: const BorderSide(
|
|
||||||
color: AppColors.krowBorder,
|
|
||||||
),
|
|
||||||
textStyle: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Container(
|
|
||||||
height: 160,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF1F3F5),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: Icon(
|
|
||||||
LucideIcons.map,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
size: 48,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Manager Contact
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'MANAGER CONTACT DETAILS',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
...(_shift.managers ?? [])
|
|
||||||
.map(
|
|
||||||
(manager) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [
|
|
||||||
AppColors.krowBlue,
|
|
||||||
Color(0xFF0830B8),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _buildAvatar(manager),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
manager.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: AppColors.krowCharcoal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
manager.phone,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(manager.phone),
|
|
||||||
duration: const Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
LucideIcons.phone,
|
|
||||||
size: 14,
|
|
||||||
color: Color(0xFF059669),
|
|
||||||
),
|
|
||||||
label: const Text(
|
|
||||||
'Call',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFF059669),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
side: const BorderSide(
|
|
||||||
color: Color(0xFFA7F3D0),
|
|
||||||
),
|
|
||||||
backgroundColor: const Color(0xFFECFDF5),
|
|
||||||
textStyle: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Additional Info
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: AppColors.krowBorder),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'ADDITIONAL INFO',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Text(
|
|
||||||
_shift.description ??
|
|
||||||
'Providing Exceptional Customer Service.',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppColors.krowMuted,
|
|
||||||
height: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Bottom Actions
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
border: Border(top: BorderSide(color: AppColors.krowBorder)),
|
|
||||||
),
|
|
||||||
child: SafeArea(
|
|
||||||
top: false,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 56,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () async {
|
|
||||||
setState(() => _isApplying = true);
|
|
||||||
try {
|
|
||||||
final acceptUseCase = Modular.get<AcceptShiftUseCase>();
|
|
||||||
await acceptUseCase(_shift.id);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _isApplying = false);
|
|
||||||
Modular.to.pop();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Shift Accepted!'),
|
|
||||||
backgroundColor: Color(0xFF10B981),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Ideally, trigger a refresh on the previous screen
|
|
||||||
Modular.get<ShiftsBloc>().add(LoadShiftsEvent());
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _isApplying = false);
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Failed to accept shift: $e'),
|
|
||||||
backgroundColor: const Color(0xFFEF4444),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: AppColors.krowBlue,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
child: _isApplying
|
|
||||||
? const SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const Text(
|
|
||||||
'Accept shift',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
try {
|
|
||||||
final declineUseCase = Modular.get<DeclineShiftUseCase>();
|
|
||||||
await declineUseCase(_shift.id);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
Modular.to.pop();
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Shift Declined'),
|
|
||||||
backgroundColor: Color(0xFFEF4444),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Refresh list
|
|
||||||
Modular.get<ShiftsBloc>().add(LoadShiftsEvent());
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (mounted) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Failed to decline shift: $e'),
|
|
||||||
backgroundColor: const Color(0xFFEF4444),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text(
|
|
||||||
'Decline shift',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Color(0xFFEF4444),
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: 14, color: text),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: TextStyle(
|
|
||||||
color: text,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Color _getStatusColor(String status) {
|
|
||||||
switch (status.toLowerCase()) {
|
|
||||||
case 'confirmed':
|
|
||||||
case 'accepted':
|
|
||||||
return const Color(0xFF10B981); // Green
|
|
||||||
case 'pending':
|
|
||||||
return const Color(0xFFF59E0B); // Yellow
|
|
||||||
case 'cancelled':
|
|
||||||
case 'rejected':
|
|
||||||
return const Color(0xFFEF4444); // Red
|
|
||||||
case 'completed':
|
|
||||||
return const Color(0xFF10B981);
|
|
||||||
default:
|
|
||||||
return AppColors.krowBlue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildAvatar(ShiftManager manager) {
|
|
||||||
if (manager.avatar != null && manager.avatar!.isNotEmpty) {
|
|
||||||
return ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Image.network(manager.avatar!, fit: BoxFit.cover),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const Center(
|
|
||||||
child: Icon(
|
|
||||||
LucideIcons.user,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDetailRow(String label, String value, bool isPositive) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(fontSize: 14, color: AppColors.krowMuted),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: isPositive ? const Color(0xFF059669) : AppColors.krowMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import '../../blocs/shifts/shifts_bloc.dart';
|
||||||
import '../../styles/shifts_styles.dart';
|
import '../../styles/shifts_styles.dart';
|
||||||
import '../my_shift_card.dart';
|
import '../my_shift_card.dart';
|
||||||
import '../shared/empty_state_view.dart';
|
import '../shared/empty_state_view.dart';
|
||||||
@@ -21,6 +23,74 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
String _searchQuery = '';
|
String _searchQuery = '';
|
||||||
String _jobType = 'all';
|
String _jobType = 'all';
|
||||||
|
|
||||||
|
void _bookShift(String id) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Book Shift'),
|
||||||
|
content: const Text(
|
||||||
|
'Do you want to instantly book this shift?',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
context.read<ShiftsBloc>().add(BookShiftEvent(id));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Shift Booking processed!'),
|
||||||
|
backgroundColor: Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
child: const Text('Book'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _declineShift(String id) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Decline Shift'),
|
||||||
|
content: const Text(
|
||||||
|
'Are you sure you want to decline this shift? It will be hidden from your available jobs.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
context.read<ShiftsBloc>().add(DeclineShiftEvent(id));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Shift Declined'),
|
||||||
|
backgroundColor: Color(0xFFEF4444),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFFEF4444),
|
||||||
|
),
|
||||||
|
child: const Text('Decline'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildFilterTab(String id, String label) {
|
Widget _buildFilterTab(String id, String label) {
|
||||||
final isSelected = _jobType == id;
|
final isSelected = _jobType == id;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -171,22 +241,8 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: MyShiftCard(
|
child: MyShiftCard(
|
||||||
shift: shift,
|
shift: shift,
|
||||||
onAccept: () {
|
onAccept: () => _bookShift(shift.id),
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
onDecline: () => _declineShift(shift.id),
|
||||||
const SnackBar(
|
|
||||||
content: Text('Shift Booked!'),
|
|
||||||
backgroundColor: Color(0xFF10B981),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onDecline: () {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Shift Declined'),
|
|
||||||
backgroundColor: Color(0xFFEF4444),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'domain/usecases/get_cancelled_shifts_usecase.dart';
|
|||||||
import 'domain/usecases/get_history_shifts_usecase.dart';
|
import 'domain/usecases/get_history_shifts_usecase.dart';
|
||||||
import 'domain/usecases/accept_shift_usecase.dart';
|
import 'domain/usecases/accept_shift_usecase.dart';
|
||||||
import 'domain/usecases/decline_shift_usecase.dart';
|
import 'domain/usecases/decline_shift_usecase.dart';
|
||||||
|
import 'domain/usecases/apply_for_shift_usecase.dart';
|
||||||
import 'domain/usecases/get_shift_details_usecase.dart';
|
import 'domain/usecases/get_shift_details_usecase.dart';
|
||||||
import 'presentation/blocs/shifts/shifts_bloc.dart';
|
import 'presentation/blocs/shifts/shifts_bloc.dart';
|
||||||
import 'presentation/pages/shifts_page.dart';
|
import 'presentation/pages/shifts_page.dart';
|
||||||
@@ -27,6 +28,7 @@ class StaffShiftsModule extends Module {
|
|||||||
i.add(GetHistoryShiftsUseCase.new);
|
i.add(GetHistoryShiftsUseCase.new);
|
||||||
i.add(AcceptShiftUseCase.new);
|
i.add(AcceptShiftUseCase.new);
|
||||||
i.add(DeclineShiftUseCase.new);
|
i.add(DeclineShiftUseCase.new);
|
||||||
|
i.add(ApplyForShiftUseCase.new);
|
||||||
i.add(GetShiftDetailsUseCase.new);
|
i.add(GetShiftDetailsUseCase.new);
|
||||||
|
|
||||||
// Bloc
|
// Bloc
|
||||||
|
|||||||
Reference in New Issue
Block a user