feat(shifts): implement submit for approval functionality
- Added `submitForApproval` method to `ShiftsRepositoryInterface` and its implementation in `ShiftsRepositoryImpl`. - Created `SubmitForApprovalUseCase` to handle the submission logic. - Updated `ShiftsBloc` to handle `SubmitForApprovalEvent` and manage submission state. - Enhanced `HistoryShiftsTab` and `MyShiftsTab` to support submission actions and display appropriate UI feedback. - Refactored date utilities for better calendar management and filtering of past shifts. - Improved UI components for better spacing and alignment. - Localized success messages for shift submission actions.
This commit is contained in:
@@ -18,6 +18,10 @@ class AssignedShift extends Equatable {
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.hourlyRateCents,
|
||||
required this.hourlyRate,
|
||||
required this.totalRateCents,
|
||||
required this.totalRate,
|
||||
required this.clientName,
|
||||
required this.orderType,
|
||||
required this.status,
|
||||
});
|
||||
@@ -33,6 +37,10 @@ class AssignedShift extends Equatable {
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: DateTime.parse(json['endTime'] as String),
|
||||
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
|
||||
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
|
||||
totalRateCents: json['totalRateCents'] as int? ?? 0,
|
||||
totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0,
|
||||
clientName: json['clientName'] as String? ?? '',
|
||||
orderType: OrderType.fromJson(json['orderType'] as String?),
|
||||
status: AssignmentStatus.fromJson(json['status'] as String?),
|
||||
);
|
||||
@@ -62,6 +70,18 @@ class AssignedShift extends Equatable {
|
||||
/// Pay rate in cents per hour.
|
||||
final int hourlyRateCents;
|
||||
|
||||
/// Pay rate in dollars per hour.
|
||||
final double hourlyRate;
|
||||
|
||||
/// Total pay for this shift in cents.
|
||||
final int totalRateCents;
|
||||
|
||||
/// Total pay for this shift in dollars.
|
||||
final double totalRate;
|
||||
|
||||
/// Name of the client / business for this shift.
|
||||
final String clientName;
|
||||
|
||||
/// Order type.
|
||||
final OrderType orderType;
|
||||
|
||||
@@ -79,6 +99,10 @@ class AssignedShift extends Equatable {
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime.toIso8601String(),
|
||||
'hourlyRateCents': hourlyRateCents,
|
||||
'hourlyRate': hourlyRate,
|
||||
'totalRateCents': totalRateCents,
|
||||
'totalRate': totalRate,
|
||||
'clientName': clientName,
|
||||
'orderType': orderType.toJson(),
|
||||
'status': status.toJson(),
|
||||
};
|
||||
@@ -94,6 +118,10 @@ class AssignedShift extends Equatable {
|
||||
startTime,
|
||||
endTime,
|
||||
hourlyRateCents,
|
||||
hourlyRate,
|
||||
totalRateCents,
|
||||
totalRate,
|
||||
clientName,
|
||||
orderType,
|
||||
status,
|
||||
];
|
||||
|
||||
@@ -12,10 +12,18 @@ class CompletedShift extends Equatable {
|
||||
required this.shiftId,
|
||||
required this.title,
|
||||
required this.location,
|
||||
required this.clientName,
|
||||
required this.date,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.minutesWorked,
|
||||
required this.hourlyRateCents,
|
||||
required this.hourlyRate,
|
||||
required this.totalRateCents,
|
||||
required this.totalRate,
|
||||
required this.paymentStatus,
|
||||
required this.status,
|
||||
this.timesheetStatus,
|
||||
});
|
||||
|
||||
/// Deserialises from the V2 API JSON response.
|
||||
@@ -25,10 +33,22 @@ class CompletedShift extends Equatable {
|
||||
shiftId: json['shiftId'] as String,
|
||||
title: json['title'] as String? ?? '',
|
||||
location: json['location'] as String? ?? '',
|
||||
clientName: json['clientName'] as String? ?? '',
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
startTime: json['startTime'] != null
|
||||
? DateTime.parse(json['startTime'] as String)
|
||||
: DateTime.now(),
|
||||
endTime: json['endTime'] != null
|
||||
? DateTime.parse(json['endTime'] as String)
|
||||
: DateTime.now(),
|
||||
minutesWorked: json['minutesWorked'] as int? ?? 0,
|
||||
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
|
||||
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
|
||||
totalRateCents: json['totalRateCents'] as int? ?? 0,
|
||||
totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0,
|
||||
paymentStatus: PaymentStatus.fromJson(json['paymentStatus'] as String?),
|
||||
status: AssignmentStatus.completed,
|
||||
timesheetStatus: json['timesheetStatus'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,18 +64,42 @@ class CompletedShift extends Equatable {
|
||||
/// Human-readable location label.
|
||||
final String location;
|
||||
|
||||
/// Name of the client / business for this shift.
|
||||
final String clientName;
|
||||
|
||||
/// The date the shift was worked.
|
||||
final DateTime date;
|
||||
|
||||
/// Scheduled start time.
|
||||
final DateTime startTime;
|
||||
|
||||
/// Scheduled end time.
|
||||
final DateTime endTime;
|
||||
|
||||
/// Total minutes worked (regular + overtime).
|
||||
final int minutesWorked;
|
||||
|
||||
/// Pay rate in cents per hour.
|
||||
final int hourlyRateCents;
|
||||
|
||||
/// Pay rate in dollars per hour.
|
||||
final double hourlyRate;
|
||||
|
||||
/// Total pay for this shift in cents.
|
||||
final int totalRateCents;
|
||||
|
||||
/// Total pay for this shift in dollars.
|
||||
final double totalRate;
|
||||
|
||||
/// Payment processing status.
|
||||
final PaymentStatus paymentStatus;
|
||||
|
||||
/// Assignment status (should always be `completed` for this class).
|
||||
final AssignmentStatus status;
|
||||
|
||||
/// Timesheet status (e.g. `SUBMITTED`, `APPROVED`, `PAID`, or null).
|
||||
final String? timesheetStatus;
|
||||
|
||||
/// Serialises to JSON.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
@@ -63,9 +107,17 @@ class CompletedShift extends Equatable {
|
||||
'shiftId': shiftId,
|
||||
'title': title,
|
||||
'location': location,
|
||||
'clientName': clientName,
|
||||
'date': date.toIso8601String(),
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime.toIso8601String(),
|
||||
'minutesWorked': minutesWorked,
|
||||
'hourlyRateCents': hourlyRateCents,
|
||||
'hourlyRate': hourlyRate,
|
||||
'totalRateCents': totalRateCents,
|
||||
'totalRate': totalRate,
|
||||
'paymentStatus': paymentStatus.toJson(),
|
||||
'timesheetStatus': timesheetStatus,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,8 +127,17 @@ class CompletedShift extends Equatable {
|
||||
shiftId,
|
||||
title,
|
||||
location,
|
||||
clientName,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
minutesWorked,
|
||||
hourlyRateCents,
|
||||
hourlyRate,
|
||||
totalRateCents,
|
||||
totalRate,
|
||||
paymentStatus,
|
||||
timesheetStatus,
|
||||
status,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class OpenShift extends Equatable {
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.hourlyRateCents,
|
||||
required this.hourlyRate,
|
||||
required this.orderType,
|
||||
required this.instantBook,
|
||||
required this.requiredWorkerCount,
|
||||
@@ -33,6 +34,7 @@ class OpenShift extends Equatable {
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: DateTime.parse(json['endTime'] as String),
|
||||
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
|
||||
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
|
||||
orderType: OrderType.fromJson(json['orderType'] as String?),
|
||||
instantBook: json['instantBook'] as bool? ?? false,
|
||||
requiredWorkerCount: json['requiredWorkerCount'] as int? ?? 1,
|
||||
@@ -63,6 +65,9 @@ class OpenShift extends Equatable {
|
||||
/// Pay rate in cents per hour.
|
||||
final int hourlyRateCents;
|
||||
|
||||
/// Pay rate in dollars per hour.
|
||||
final double hourlyRate;
|
||||
|
||||
/// Order type.
|
||||
final OrderType orderType;
|
||||
|
||||
@@ -83,6 +88,7 @@ class OpenShift extends Equatable {
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime.toIso8601String(),
|
||||
'hourlyRateCents': hourlyRateCents,
|
||||
'hourlyRate': hourlyRate,
|
||||
'orderType': orderType.toJson(),
|
||||
'instantBook': instantBook,
|
||||
'requiredWorkerCount': requiredWorkerCount,
|
||||
@@ -99,6 +105,7 @@ class OpenShift extends Equatable {
|
||||
startTime,
|
||||
endTime,
|
||||
hourlyRateCents,
|
||||
hourlyRate,
|
||||
orderType,
|
||||
instantBook,
|
||||
requiredWorkerCount,
|
||||
|
||||
@@ -11,7 +11,7 @@ class Shift extends Equatable {
|
||||
/// Creates a [Shift].
|
||||
const Shift({
|
||||
required this.id,
|
||||
required this.orderId,
|
||||
this.orderId,
|
||||
required this.title,
|
||||
required this.status,
|
||||
required this.startsAt,
|
||||
@@ -25,13 +25,16 @@ class Shift extends Equatable {
|
||||
required this.requiredWorkers,
|
||||
required this.assignedWorkers,
|
||||
this.notes,
|
||||
this.clockInMode,
|
||||
this.allowClockInOverride,
|
||||
this.nfcTagId,
|
||||
});
|
||||
|
||||
/// Deserialises from the V2 API JSON response.
|
||||
factory Shift.fromJson(Map<String, dynamic> json) {
|
||||
return Shift(
|
||||
id: json['id'] as String,
|
||||
orderId: json['orderId'] as String,
|
||||
orderId: json['orderId'] as String?,
|
||||
title: json['title'] as String? ?? '',
|
||||
status: ShiftStatus.fromJson(json['status'] as String?),
|
||||
startsAt: DateTime.parse(json['startsAt'] as String),
|
||||
@@ -45,14 +48,17 @@ class Shift extends Equatable {
|
||||
requiredWorkers: json['requiredWorkers'] as int? ?? 1,
|
||||
assignedWorkers: json['assignedWorkers'] as int? ?? 0,
|
||||
notes: json['notes'] as String?,
|
||||
clockInMode: json['clockInMode'] as String?,
|
||||
allowClockInOverride: json['allowClockInOverride'] as bool?,
|
||||
nfcTagId: json['nfcTagId'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// The shift row id.
|
||||
final String id;
|
||||
|
||||
/// The parent order id.
|
||||
final String orderId;
|
||||
/// The parent order id (may be null for today-shifts endpoint).
|
||||
final String? orderId;
|
||||
|
||||
/// Display title.
|
||||
final String title;
|
||||
@@ -93,6 +99,15 @@ class Shift extends Equatable {
|
||||
/// Free-form notes for the shift.
|
||||
final String? notes;
|
||||
|
||||
/// Clock-in mode for this shift (`NFC_REQUIRED`, `GEO_REQUIRED`, `EITHER`).
|
||||
final String? clockInMode;
|
||||
|
||||
/// Whether the worker is allowed to override the clock-in method.
|
||||
final bool? allowClockInOverride;
|
||||
|
||||
/// NFC tag identifier for NFC-based clock-in.
|
||||
final String? nfcTagId;
|
||||
|
||||
/// Serialises to JSON.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
@@ -111,6 +126,9 @@ class Shift extends Equatable {
|
||||
'requiredWorkers': requiredWorkers,
|
||||
'assignedWorkers': assignedWorkers,
|
||||
'notes': notes,
|
||||
'clockInMode': clockInMode,
|
||||
'allowClockInOverride': allowClockInOverride,
|
||||
'nfcTagId': nfcTagId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -140,5 +158,8 @@ class Shift extends Equatable {
|
||||
requiredWorkers,
|
||||
assignedWorkers,
|
||||
notes,
|
||||
clockInMode,
|
||||
allowClockInOverride,
|
||||
nfcTagId,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/src/entities/enums/application_status.dart';
|
||||
import 'package:krow_domain/src/entities/enums/assignment_status.dart';
|
||||
import 'package:krow_domain/src/entities/enums/order_type.dart';
|
||||
import 'package:krow_domain/src/entities/shifts/shift.dart';
|
||||
|
||||
/// Full detail view of a shift for the staff member.
|
||||
///
|
||||
@@ -18,17 +19,27 @@ class ShiftDetail extends Equatable {
|
||||
this.description,
|
||||
required this.location,
|
||||
this.address,
|
||||
required this.clientName,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
required this.date,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.roleId,
|
||||
required this.roleName,
|
||||
required this.hourlyRateCents,
|
||||
required this.hourlyRate,
|
||||
required this.totalRateCents,
|
||||
required this.totalRate,
|
||||
required this.orderType,
|
||||
required this.requiredCount,
|
||||
required this.confirmedCount,
|
||||
this.assignmentStatus,
|
||||
this.applicationStatus,
|
||||
this.clockInMode,
|
||||
required this.allowClockInOverride,
|
||||
this.geofenceRadiusMeters,
|
||||
this.nfcTagId,
|
||||
});
|
||||
|
||||
/// Deserialises from the V2 API JSON response.
|
||||
@@ -39,12 +50,18 @@ class ShiftDetail extends Equatable {
|
||||
description: json['description'] as String?,
|
||||
location: json['location'] as String? ?? '',
|
||||
address: json['address'] as String?,
|
||||
clientName: json['clientName'] as String? ?? '',
|
||||
latitude: Shift.parseDouble(json['latitude']),
|
||||
longitude: Shift.parseDouble(json['longitude']),
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: DateTime.parse(json['endTime'] as String),
|
||||
roleId: json['roleId'] as String,
|
||||
roleName: json['roleName'] as String,
|
||||
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
|
||||
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
|
||||
totalRateCents: json['totalRateCents'] as int? ?? 0,
|
||||
totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0,
|
||||
orderType: OrderType.fromJson(json['orderType'] as String?),
|
||||
requiredCount: json['requiredCount'] as int? ?? 1,
|
||||
confirmedCount: json['confirmedCount'] as int? ?? 0,
|
||||
@@ -54,6 +71,10 @@ class ShiftDetail extends Equatable {
|
||||
applicationStatus: json['applicationStatus'] != null
|
||||
? ApplicationStatus.fromJson(json['applicationStatus'] as String?)
|
||||
: null,
|
||||
clockInMode: json['clockInMode'] as String?,
|
||||
allowClockInOverride: json['allowClockInOverride'] as bool? ?? false,
|
||||
geofenceRadiusMeters: json['geofenceRadiusMeters'] as int?,
|
||||
nfcTagId: json['nfcTagId'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,6 +93,15 @@ class ShiftDetail extends Equatable {
|
||||
/// Street address of the shift location.
|
||||
final String? address;
|
||||
|
||||
/// Name of the client / business for this shift.
|
||||
final String clientName;
|
||||
|
||||
/// Latitude for map display and geofence validation.
|
||||
final double? latitude;
|
||||
|
||||
/// Longitude for map display and geofence validation.
|
||||
final double? longitude;
|
||||
|
||||
/// Date of the shift (same as startTime, kept for display grouping).
|
||||
final DateTime date;
|
||||
|
||||
@@ -90,6 +120,15 @@ class ShiftDetail extends Equatable {
|
||||
/// Pay rate in cents per hour.
|
||||
final int hourlyRateCents;
|
||||
|
||||
/// Pay rate in dollars per hour.
|
||||
final double hourlyRate;
|
||||
|
||||
/// Total pay for this shift in cents.
|
||||
final int totalRateCents;
|
||||
|
||||
/// Total pay for this shift in dollars.
|
||||
final double totalRate;
|
||||
|
||||
/// Order type.
|
||||
final OrderType orderType;
|
||||
|
||||
@@ -105,6 +144,26 @@ class ShiftDetail extends Equatable {
|
||||
/// Current worker's application status, if applied.
|
||||
final ApplicationStatus? applicationStatus;
|
||||
|
||||
/// Clock-in mode for this shift (`NFC_REQUIRED`, `GEO_REQUIRED`, `EITHER`).
|
||||
final String? clockInMode;
|
||||
|
||||
/// Whether the worker is allowed to override the clock-in method.
|
||||
final bool allowClockInOverride;
|
||||
|
||||
/// Geofence radius in meters for clock-in validation.
|
||||
final int? geofenceRadiusMeters;
|
||||
|
||||
/// NFC tag identifier for NFC-based clock-in.
|
||||
final String? nfcTagId;
|
||||
|
||||
/// Duration of the shift in hours.
|
||||
double get durationHours {
|
||||
return endTime.difference(startTime).inMinutes / 60;
|
||||
}
|
||||
|
||||
/// Estimated total pay in dollars.
|
||||
double get estimatedTotal => hourlyRate * durationHours;
|
||||
|
||||
/// Serialises to JSON.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
@@ -113,17 +172,27 @@ class ShiftDetail extends Equatable {
|
||||
'description': description,
|
||||
'location': location,
|
||||
'address': address,
|
||||
'clientName': clientName,
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'date': date.toIso8601String(),
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime.toIso8601String(),
|
||||
'roleId': roleId,
|
||||
'roleName': roleName,
|
||||
'hourlyRateCents': hourlyRateCents,
|
||||
'hourlyRate': hourlyRate,
|
||||
'totalRateCents': totalRateCents,
|
||||
'totalRate': totalRate,
|
||||
'orderType': orderType.toJson(),
|
||||
'requiredCount': requiredCount,
|
||||
'confirmedCount': confirmedCount,
|
||||
'assignmentStatus': assignmentStatus?.toJson(),
|
||||
'applicationStatus': applicationStatus?.toJson(),
|
||||
'clockInMode': clockInMode,
|
||||
'allowClockInOverride': allowClockInOverride,
|
||||
'geofenceRadiusMeters': geofenceRadiusMeters,
|
||||
'nfcTagId': nfcTagId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,16 +203,26 @@ class ShiftDetail extends Equatable {
|
||||
description,
|
||||
location,
|
||||
address,
|
||||
clientName,
|
||||
latitude,
|
||||
longitude,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
roleId,
|
||||
roleName,
|
||||
hourlyRateCents,
|
||||
hourlyRate,
|
||||
totalRateCents,
|
||||
totalRate,
|
||||
orderType,
|
||||
requiredCount,
|
||||
confirmedCount,
|
||||
assignmentStatus,
|
||||
applicationStatus,
|
||||
clockInMode,
|
||||
allowClockInOverride,
|
||||
geofenceRadiusMeters,
|
||||
nfcTagId,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user