feat: Integrate Google Maps Places Autocomplete for Hub Address Validation
- Refactored ShiftsBloc to remove unused shift-related events and use cases. - Updated navigation paths in ShiftsNavigator to reflect new structure. - Simplified MyShiftCard widget by removing unnecessary parameters and logic. - Modified FindShiftsTab and HistoryShiftsTab to utilize new navigation for shift details. - Created ShiftDetailsModule with necessary bindings and routes for shift details. - Implemented ShiftDetailsBloc, ShiftDetailsEvent, and ShiftDetailsState for managing shift details. - Developed ShiftDetailsPage to display detailed information about a shift and handle booking/declining actions. - Added necessary imports and adjusted existing files to accommodate new shift details functionality.
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import '../../../domain/usecases/apply_for_shift_usecase.dart';
|
||||||
|
import '../../../domain/usecases/decline_shift_usecase.dart';
|
||||||
|
import '../../../domain/usecases/get_shift_details_usecase.dart';
|
||||||
|
import 'shift_details_event.dart';
|
||||||
|
import 'shift_details_state.dart';
|
||||||
|
|
||||||
|
class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
|
||||||
|
final GetShiftDetailsUseCase getShiftDetails;
|
||||||
|
final ApplyForShiftUseCase applyForShift;
|
||||||
|
final DeclineShiftUseCase declineShift;
|
||||||
|
|
||||||
|
ShiftDetailsBloc({
|
||||||
|
required this.getShiftDetails,
|
||||||
|
required this.applyForShift,
|
||||||
|
required this.declineShift,
|
||||||
|
}) : super(ShiftDetailsInitial()) {
|
||||||
|
on<LoadShiftDetailsEvent>(_onLoadDetails);
|
||||||
|
on<BookShiftDetailsEvent>(_onBookShift);
|
||||||
|
on<DeclineShiftDetailsEvent>(_onDeclineShift);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadDetails(
|
||||||
|
LoadShiftDetailsEvent event,
|
||||||
|
Emitter<ShiftDetailsState> emit,
|
||||||
|
) async {
|
||||||
|
emit(ShiftDetailsLoading());
|
||||||
|
try {
|
||||||
|
final shift = await getShiftDetails(event.shiftId);
|
||||||
|
if (shift != null) {
|
||||||
|
emit(ShiftDetailsLoaded(shift));
|
||||||
|
} else {
|
||||||
|
emit(const ShiftDetailsError("Shift not found"));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(ShiftDetailsError(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onBookShift(
|
||||||
|
BookShiftDetailsEvent event,
|
||||||
|
Emitter<ShiftDetailsState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await applyForShift(event.shiftId, isInstantBook: true);
|
||||||
|
emit(const ShiftActionSuccess("Shift successfully booked!"));
|
||||||
|
} catch (e) {
|
||||||
|
emit(ShiftDetailsError(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDeclineShift(
|
||||||
|
DeclineShiftDetailsEvent event,
|
||||||
|
Emitter<ShiftDetailsState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
await declineShift(event.shiftId);
|
||||||
|
emit(const ShiftActionSuccess("Shift declined"));
|
||||||
|
} catch (e) {
|
||||||
|
emit(ShiftDetailsError(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
|
abstract class ShiftDetailsEvent extends Equatable {
|
||||||
|
const ShiftDetailsEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadShiftDetailsEvent extends ShiftDetailsEvent {
|
||||||
|
final String shiftId;
|
||||||
|
const LoadShiftDetailsEvent(this.shiftId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [shiftId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookShiftDetailsEvent extends ShiftDetailsEvent {
|
||||||
|
final String shiftId;
|
||||||
|
const BookShiftDetailsEvent(this.shiftId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [shiftId];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeclineShiftDetailsEvent extends ShiftDetailsEvent {
|
||||||
|
final String shiftId;
|
||||||
|
const DeclineShiftDetailsEvent(this.shiftId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [shiftId];
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
abstract class ShiftDetailsState extends Equatable {
|
||||||
|
const ShiftDetailsState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShiftDetailsInitial extends ShiftDetailsState {}
|
||||||
|
|
||||||
|
class ShiftDetailsLoading extends ShiftDetailsState {}
|
||||||
|
|
||||||
|
class ShiftDetailsLoaded extends ShiftDetailsState {
|
||||||
|
final Shift shift;
|
||||||
|
const ShiftDetailsLoaded(this.shift);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [shift];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShiftDetailsError extends ShiftDetailsState {
|
||||||
|
final String message;
|
||||||
|
const ShiftDetailsError(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShiftActionSuccess extends ShiftDetailsState {
|
||||||
|
final String message;
|
||||||
|
const ShiftActionSuccess(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [message];
|
||||||
|
}
|
||||||
@@ -3,15 +3,12 @@ import 'package:equatable/equatable.dart';
|
|||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../../../domain/usecases/get_available_shifts_usecase.dart';
|
|
||||||
import '../../../domain/arguments/get_available_shifts_arguments.dart';
|
import '../../../domain/arguments/get_available_shifts_arguments.dart';
|
||||||
import '../../../domain/usecases/get_my_shifts_usecase.dart';
|
import '../../../domain/usecases/get_available_shifts_usecase.dart';
|
||||||
import '../../../domain/usecases/get_pending_assignments_usecase.dart';
|
|
||||||
import '../../../domain/usecases/get_cancelled_shifts_usecase.dart';
|
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/get_my_shifts_usecase.dart';
|
||||||
import '../../../domain/usecases/decline_shift_usecase.dart';
|
import '../../../domain/usecases/get_pending_assignments_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';
|
||||||
@@ -22,9 +19,6 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
final GetPendingAssignmentsUseCase getPendingAssignments;
|
final GetPendingAssignmentsUseCase getPendingAssignments;
|
||||||
final GetCancelledShiftsUseCase getCancelledShifts;
|
final GetCancelledShiftsUseCase getCancelledShifts;
|
||||||
final GetHistoryShiftsUseCase getHistoryShifts;
|
final GetHistoryShiftsUseCase getHistoryShifts;
|
||||||
final AcceptShiftUseCase acceptShift;
|
|
||||||
final DeclineShiftUseCase declineShift;
|
|
||||||
final ApplyForShiftUseCase applyForShift;
|
|
||||||
|
|
||||||
ShiftsBloc({
|
ShiftsBloc({
|
||||||
required this.getMyShifts,
|
required this.getMyShifts,
|
||||||
@@ -32,15 +26,9 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
required this.getPendingAssignments,
|
required this.getPendingAssignments,
|
||||||
required this.getCancelledShifts,
|
required this.getCancelledShifts,
|
||||||
required this.getHistoryShifts,
|
required this.getHistoryShifts,
|
||||||
required this.acceptShift,
|
|
||||||
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<DeclineShiftEvent>(_onDeclineShift);
|
|
||||||
on<BookShiftEvent>(_onBookShift);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadShifts(
|
Future<void> _onLoadShifts(
|
||||||
@@ -102,40 +90,4 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAcceptShift(
|
|
||||||
AcceptShiftEvent event,
|
|
||||||
Emitter<ShiftsState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await acceptShift(event.shiftId);
|
|
||||||
add(LoadShiftsEvent()); // Reload lists
|
|
||||||
} catch (_) {
|
|
||||||
// Handle error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onDeclineShift(
|
|
||||||
DeclineShiftEvent event,
|
|
||||||
Emitter<ShiftsState> emit,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
await declineShift(event.shiftId);
|
|
||||||
add(LoadShiftsEvent()); // Reload lists
|
|
||||||
} catch (_) {
|
|
||||||
// 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,11 +35,3 @@ 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];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
|
|
||||||
extension ShiftsNavigator on IModularNavigator {
|
extension ShiftsNavigator on IModularNavigator {
|
||||||
void pushShiftDetails(Shift shift) {
|
void pushShiftDetails(Shift shift) {
|
||||||
pushNamed('/shifts/details/${shift.id}', arguments: shift);
|
pushNamed('/worker-main/shift-details/${shift.id}', arguments: shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example for going back or internal navigation if needed
|
// Example for going back or internal navigation if needed
|
||||||
|
|||||||
@@ -0,0 +1,433 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
import 'package:design_system/design_system.dart'; // Re-added for UiIcons/Colors as they are used in expanded logic
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import '../blocs/shift_details/shift_details_bloc.dart';
|
||||||
|
import '../blocs/shift_details/shift_details_event.dart';
|
||||||
|
import '../blocs/shift_details/shift_details_state.dart';
|
||||||
|
import '../styles/shifts_styles.dart';
|
||||||
|
import '../widgets/my_shift_card.dart';
|
||||||
|
|
||||||
|
class ShiftDetailsPage extends StatelessWidget {
|
||||||
|
final String shiftId;
|
||||||
|
final Shift? shift;
|
||||||
|
|
||||||
|
const ShiftDetailsPage({
|
||||||
|
super.key,
|
||||||
|
required this.shiftId,
|
||||||
|
this.shift,
|
||||||
|
});
|
||||||
|
|
||||||
|
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:mm a').format(dt);
|
||||||
|
} catch (e) {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _calculateDuration(Shift shift) {
|
||||||
|
if (shift.startTime.isEmpty || shift.endTime.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final s = shift.startTime.split(':').map(int.parse).toList();
|
||||||
|
final e = shift.endTime.split(':').map(int.parse).toList();
|
||||||
|
double hours = ((e[0] * 60 + e[1]) - (s[0] * 60 + s[1])) / 60;
|
||||||
|
if (hours < 0) hours += 24;
|
||||||
|
return hours.roundToDouble();
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatCard(IconData icon, String value, String label) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF8FAFC),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: UiColors.border),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, size: 20, color: UiColors.iconSecondary),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: UiTypography.title1m.copyWith(
|
||||||
|
color: UiColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: UiTypography.footnote2r.copyWith(
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTimeBox(String label, String time) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF8FAFC),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_formatTime(time),
|
||||||
|
style: UiTypography.display2m.copyWith(
|
||||||
|
fontSize: 20,
|
||||||
|
color: UiColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => Modular.get<ShiftDetailsBloc>()
|
||||||
|
..add(LoadShiftDetailsEvent(shiftId)),
|
||||||
|
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is ShiftActionSuccess) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Modular.to.pop(true); // Return outcome
|
||||||
|
} else if (state is ShiftDetailsError) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: const Color(0xFFEF4444),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: BlocBuilder<ShiftDetailsBloc, ShiftDetailsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is ShiftDetailsLoading) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Shift? displayShift;
|
||||||
|
if (state is ShiftDetailsLoaded) {
|
||||||
|
displayShift = state.shift;
|
||||||
|
} else {
|
||||||
|
displayShift = shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayShift == null) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: Text("Shift not found")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final duration = _calculateDuration(displayShift);
|
||||||
|
final estimatedTotal = (displayShift.hourlyRate) * duration;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.krowBackground,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text("Shift Details"),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: AppColors.krowCharcoal,
|
||||||
|
elevation: 0.5,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
MyShiftCard(
|
||||||
|
shift: displayShift,
|
||||||
|
// No direct actions on the card, handled by page buttons
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Stats Row
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
UiIcons.dollar,
|
||||||
|
"\$${estimatedTotal.toStringAsFixed(0)}",
|
||||||
|
"Total",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
UiIcons.dollar,
|
||||||
|
"\$${displayShift.hourlyRate.toInt()}",
|
||||||
|
"Hourly Rate",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
UiIcons.clock,
|
||||||
|
"${duration.toInt()}",
|
||||||
|
"Hours",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// In/Out Time
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildTimeBox(
|
||||||
|
"CLOCK IN TIME",
|
||||||
|
displayShift.startTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildTimeBox(
|
||||||
|
"CLOCK OUT TIME",
|
||||||
|
displayShift.endTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Location
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"LOCATION",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
displayShift.location.isEmpty
|
||||||
|
? "TBD"
|
||||||
|
: displayShift.location,
|
||||||
|
style: UiTypography.title1m.copyWith(
|
||||||
|
color: UiColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(displayShift!.locationAddress),
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(UiIcons.navigation, size: 14),
|
||||||
|
label: const Text(
|
||||||
|
"Get direction",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: UiColors.textPrimary,
|
||||||
|
side: const BorderSide(color: UiColors.border),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
|
||||||
|
minimumSize: const Size(0, 32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
height: 128,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Icon(
|
||||||
|
UiIcons.mapPin,
|
||||||
|
color: UiColors.iconSecondary,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Placeholder for Map
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Additional Info
|
||||||
|
if (displayShift.description != null) ...[
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"ADDITIONAL INFO",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: UiColors.textSecondary,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
displayShift.description!,
|
||||||
|
style: UiTypography.body2m.copyWith(
|
||||||
|
color: UiColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () => _declineShift(context, displayShift!.id),
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFFEF4444),
|
||||||
|
side: const BorderSide(color: Color(0xFFEF4444)),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
child: const Text("Decline"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => _bookShift(context, displayShift!.id),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF10B981),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
child: const Text("Book Shift"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: MediaQuery.of(context).padding.bottom + 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _bookShift(BuildContext context, String id) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: const Text('Book Shift'),
|
||||||
|
content: const Text('Do you want to instantly book this shift?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
BlocProvider.of<ShiftDetailsBloc>(context).add(BookShiftDetailsEvent(id));
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFF10B981),
|
||||||
|
),
|
||||||
|
child: const Text('Book'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _declineShift(BuildContext context, String id) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => 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(ctx).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
BlocProvider.of<ShiftDetailsBloc>(context).add(DeclineShiftDetailsEvent(id));
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFFEF4444),
|
||||||
|
),
|
||||||
|
child: const Text('Decline'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:core_localization/core_localization.dart';
|
import 'package:core_localization/core_localization.dart';
|
||||||
|
import 'package:staff_shifts/src/presentation/navigation/shifts_navigator.dart';
|
||||||
|
|
||||||
class MyShiftCard extends StatefulWidget {
|
class MyShiftCard extends StatefulWidget {
|
||||||
final Shift shift;
|
final Shift shift;
|
||||||
final bool historyMode;
|
|
||||||
final VoidCallback? onAccept;
|
|
||||||
final VoidCallback? onDecline;
|
|
||||||
final VoidCallback? onRequestSwap;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
const MyShiftCard({
|
const MyShiftCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.shift,
|
required this.shift,
|
||||||
this.historyMode = false,
|
|
||||||
this.onAccept,
|
|
||||||
this.onDecline,
|
|
||||||
this.onRequestSwap,
|
|
||||||
this.index = 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -27,8 +19,6 @@ class MyShiftCard extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyShiftCardState extends State<MyShiftCard> {
|
class _MyShiftCardState extends State<MyShiftCard> {
|
||||||
bool _isExpanded = false;
|
|
||||||
|
|
||||||
String _formatTime(String time) {
|
String _formatTime(String time) {
|
||||||
if (time.isEmpty) return '';
|
if (time.isEmpty) return '';
|
||||||
try {
|
try {
|
||||||
@@ -120,9 +110,10 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => setState(() => _isExpanded = !_isExpanded),
|
onTap: () {
|
||||||
child: AnimatedContainer(
|
Modular.to.pushShiftDetails(widget.shift);
|
||||||
duration: const Duration(milliseconds: 300),
|
},
|
||||||
|
child: Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -389,384 +380,9 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Expanded Content
|
|
||||||
AnimatedSize(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: _isExpanded
|
|
||||||
? Column(
|
|
||||||
children: [
|
|
||||||
const Divider(height: 1, color: UiColors.border),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
// Stats Row
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
UiIcons.dollar,
|
|
||||||
"\$${estimatedTotal.toStringAsFixed(0)}",
|
|
||||||
"Total",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
UiIcons.dollar,
|
|
||||||
"\$${widget.shift.hourlyRate.toInt()}",
|
|
||||||
"Hourly Rate",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
UiIcons.clock,
|
|
||||||
"${duration.toInt()}",
|
|
||||||
"Hours",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// In/Out Time
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildTimeBox(
|
|
||||||
"CLOCK IN TIME",
|
|
||||||
widget.shift.startTime,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: _buildTimeBox(
|
|
||||||
"CLOCK OUT TIME",
|
|
||||||
widget.shift.endTime,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Location
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"LOCATION",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.shift.location.isEmpty
|
|
||||||
? "TBD"
|
|
||||||
: widget.shift.location,
|
|
||||||
style: UiTypography.title1m.copyWith(
|
|
||||||
color: UiColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Show snackbar with the address
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
widget.shift.locationAddress,
|
|
||||||
),
|
|
||||||
duration: const Duration(
|
|
||||||
seconds: 3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.navigation,
|
|
||||||
size: 14,
|
|
||||||
),
|
|
||||||
label: const Text(
|
|
||||||
"Get direction",
|
|
||||||
style: TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: UiColors.textPrimary,
|
|
||||||
side: const BorderSide(
|
|
||||||
color: UiColors.border,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12,
|
|
||||||
vertical: 0,
|
|
||||||
),
|
|
||||||
minimumSize: const Size(0, 32),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Container(
|
|
||||||
height: 128,
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade100,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: const Center(
|
|
||||||
child: Icon(
|
|
||||||
UiIcons.mapPin,
|
|
||||||
color: UiColors.iconSecondary,
|
|
||||||
size: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Placeholder for Map
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Additional Info
|
|
||||||
if (widget.shift.description != null) ...[
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"ADDITIONAL INFO",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
widget.shift.description!.split('.')[0],
|
|
||||||
style: UiTypography.body2m.copyWith(
|
|
||||||
color: UiColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
widget.shift.description!,
|
|
||||||
style: UiTypography.body3r.copyWith(
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
if (!widget.historyMode)
|
|
||||||
if (status == 'confirmed')
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton.icon(
|
|
||||||
onPressed: widget.onRequestSwap,
|
|
||||||
icon: const Icon(
|
|
||||||
UiIcons.swap,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
label: Text(
|
|
||||||
t.staff_shifts.action.request_swap),
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: UiColors.primary,
|
|
||||||
side: const BorderSide(
|
|
||||||
color: UiColors.primary,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(
|
|
||||||
12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else if (status == 'swap')
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(
|
|
||||||
0xFFFFFBEB,
|
|
||||||
), // amber-50
|
|
||||||
border: Border.all(
|
|
||||||
color: const Color(0xFFFDE68A),
|
|
||||||
), // amber-200
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
UiIcons.swap,
|
|
||||||
size: 16,
|
|
||||||
color: Color(0xFFB45309),
|
|
||||||
), // amber-700
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
t.staff_shifts.status.swap_requested,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Color(0xFFB45309),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: widget.onAccept,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: UiColors.primary,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
status == 'pending'
|
|
||||||
? t.staff_shifts.action.confirm
|
|
||||||
: "Book Shift",
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (status == 'pending' ||
|
|
||||||
status == 'open') ...[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 48,
|
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: widget.onDecline,
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor:
|
|
||||||
UiColors.destructive,
|
|
||||||
side: const BorderSide(
|
|
||||||
color: UiColors.border,
|
|
||||||
),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
t.staff_shifts.action.decline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatCard(IconData icon, String value, String label) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF8FAFC),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: UiColors.border),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(icon, size: 20, color: UiColors.iconSecondary),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: UiTypography.title1m.copyWith(
|
|
||||||
color: UiColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: UiTypography.footnote2r.copyWith(
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTimeBox(String label, String time) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF8FAFC),
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: UiColors.textSecondary,
|
|
||||||
letterSpacing: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
_formatTime(time),
|
|
||||||
style: UiTypography.display2m.copyWith(
|
|
||||||
fontSize: 20,
|
|
||||||
color: UiColors.textPrimary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_modular/flutter_modular.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 '../../navigation/shifts_navigator.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';
|
||||||
@@ -23,74 +23,6 @@ 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(
|
||||||
@@ -241,8 +173,6 @@ 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: () => _bookShift(shift.id),
|
|
||||||
onDecline: () => _declineShift(shift.id),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import '../../navigation/shifts_navigator.dart';
|
||||||
import '../my_shift_card.dart';
|
import '../my_shift_card.dart';
|
||||||
import '../shared/empty_state_view.dart';
|
import '../shared/empty_state_view.dart';
|
||||||
|
|
||||||
@@ -30,9 +32,11 @@ class HistoryShiftsTab extends StatelessWidget {
|
|||||||
...historyShifts.map(
|
...historyShifts.map(
|
||||||
(shift) => Padding(
|
(shift) => Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Modular.to.pushShiftDetails(shift),
|
||||||
child: MyShiftCard(
|
child: MyShiftCard(
|
||||||
shift: shift,
|
shift: shift,
|
||||||
historyMode: true,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'domain/repositories/shifts_repository_interface.dart';
|
||||||
|
import 'data/repositories_impl/shifts_repository_impl.dart';
|
||||||
|
import 'domain/usecases/get_shift_details_usecase.dart';
|
||||||
|
import 'domain/usecases/accept_shift_usecase.dart';
|
||||||
|
import 'domain/usecases/decline_shift_usecase.dart';
|
||||||
|
import 'domain/usecases/apply_for_shift_usecase.dart';
|
||||||
|
import 'presentation/blocs/shift_details/shift_details_bloc.dart';
|
||||||
|
import 'presentation/pages/shift_details_page.dart';
|
||||||
|
|
||||||
|
class ShiftDetailsModule extends Module {
|
||||||
|
@override
|
||||||
|
void binds(Injector i) {
|
||||||
|
// Repository
|
||||||
|
i.add<ShiftsRepositoryInterface>(ShiftsRepositoryImpl.new);
|
||||||
|
|
||||||
|
// UseCases
|
||||||
|
i.add(GetShiftDetailsUseCase.new);
|
||||||
|
i.add(AcceptShiftUseCase.new);
|
||||||
|
i.add(DeclineShiftUseCase.new);
|
||||||
|
i.add(ApplyForShiftUseCase.new);
|
||||||
|
|
||||||
|
// Bloc
|
||||||
|
i.add(ShiftDetailsBloc.new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void routes(RouteManager r) {
|
||||||
|
r.child('/:id', child: (_) => ShiftDetailsPage(shiftId: r.args.params['id'], shift: r.args.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import 'domain/usecases/decline_shift_usecase.dart';
|
|||||||
import 'domain/usecases/apply_for_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/blocs/shift_details/shift_details_bloc.dart';
|
||||||
import 'presentation/pages/shifts_page.dart';
|
import 'presentation/pages/shifts_page.dart';
|
||||||
import 'presentation/pages/shift_details_page.dart';
|
import 'presentation/pages/shift_details_page.dart';
|
||||||
|
|
||||||
@@ -33,11 +34,11 @@ class StaffShiftsModule extends Module {
|
|||||||
|
|
||||||
// Bloc
|
// Bloc
|
||||||
i.add(ShiftsBloc.new);
|
i.add(ShiftsBloc.new);
|
||||||
|
i.add(ShiftDetailsBloc.new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void routes(RouteManager r) {
|
void routes(RouteManager r) {
|
||||||
r.child('/', child: (_) => const ShiftsPage());
|
r.child('/', child: (_) => const ShiftsPage());
|
||||||
r.child('/details/:id', child: (_) => ShiftDetailsPage(shiftId: r.args.params['id'], shift: r.args.data));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
library staff_shifts;
|
library staff_shifts;
|
||||||
|
|
||||||
export 'src/staff_shifts_module.dart';
|
export 'src/staff_shifts_module.dart';
|
||||||
|
export 'src/shift_details_module.dart';
|
||||||
|
export 'src/presentation/navigation/shifts_navigator.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ class StaffMainModule extends Module {
|
|||||||
'/availability',
|
'/availability',
|
||||||
module: StaffAvailabilityModule(),
|
module: StaffAvailabilityModule(),
|
||||||
);
|
);
|
||||||
|
r.module(
|
||||||
|
'/shift-details',
|
||||||
|
module: ShiftDetailsModule(),
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user