Merge pull request #348 from Oloodi/fix_staff_app_bugs
Fix staff app bugs
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
# Basic Usage
|
# Basic Usage
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
ExampleConnector.instance.CreateStaff(createStaffVariables).execute();
|
ExampleConnector.instance.createTaskComment(createTaskCommentVariables).execute();
|
||||||
ExampleConnector.instance.UpdateStaff(updateStaffVariables).execute();
|
ExampleConnector.instance.updateTaskComment(updateTaskCommentVariables).execute();
|
||||||
ExampleConnector.instance.DeleteStaff(deleteStaffVariables).execute();
|
ExampleConnector.instance.deleteTaskComment(deleteTaskCommentVariables).execute();
|
||||||
ExampleConnector.instance.listStaffAvailabilities(listStaffAvailabilitiesVariables).execute();
|
ExampleConnector.instance.createTaxForm(createTaxFormVariables).execute();
|
||||||
ExampleConnector.instance.listStaffAvailabilitiesByStaffId(listStaffAvailabilitiesByStaffIdVariables).execute();
|
ExampleConnector.instance.updateTaxForm(updateTaxFormVariables).execute();
|
||||||
ExampleConnector.instance.getStaffAvailabilityByKey(getStaffAvailabilityByKeyVariables).execute();
|
ExampleConnector.instance.deleteTaxForm(deleteTaxFormVariables).execute();
|
||||||
ExampleConnector.instance.listStaffAvailabilitiesByDay(listStaffAvailabilitiesByDayVariables).execute();
|
ExampleConnector.instance.createUserConversation(createUserConversationVariables).execute();
|
||||||
ExampleConnector.instance.createStaffAvailabilityStats(createStaffAvailabilityStatsVariables).execute();
|
ExampleConnector.instance.updateUserConversation(updateUserConversationVariables).execute();
|
||||||
ExampleConnector.instance.updateStaffAvailabilityStats(updateStaffAvailabilityStatsVariables).execute();
|
ExampleConnector.instance.markConversationAsRead(markConversationAsReadVariables).execute();
|
||||||
ExampleConnector.instance.deleteStaffAvailabilityStats(deleteStaffAvailabilityStatsVariables).execute();
|
ExampleConnector.instance.incrementUnreadForUser(incrementUnreadForUserVariables).execute();
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Optional fields can be discovered based on classes that have `Optional` object t
|
|||||||
This is an example of a mutation with an optional field:
|
This is an example of a mutation with an optional field:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
await ExampleConnector.instance.searchInvoiceTemplatesByOwnerAndName({ ... })
|
await ExampleConnector.instance.listStaffAvailabilitiesByDay({ ... })
|
||||||
.offset(...)
|
.offset(...)
|
||||||
.execute();
|
.execute();
|
||||||
```
|
```
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ class UpdateApplicationStatusVariablesBuilder {
|
|||||||
Optional<ApplicationStatus> _status = Optional.optional((data) => ApplicationStatus.values.byName(data), enumSerializer);
|
Optional<ApplicationStatus> _status = Optional.optional((data) => ApplicationStatus.values.byName(data), enumSerializer);
|
||||||
Optional<Timestamp> _checkInTime = Optional.optional((json) => json['checkInTime'] = Timestamp.fromJson(json['checkInTime']), defaultSerializer);
|
Optional<Timestamp> _checkInTime = Optional.optional((json) => json['checkInTime'] = Timestamp.fromJson(json['checkInTime']), defaultSerializer);
|
||||||
Optional<Timestamp> _checkOutTime = Optional.optional((json) => json['checkOutTime'] = Timestamp.fromJson(json['checkOutTime']), defaultSerializer);
|
Optional<Timestamp> _checkOutTime = Optional.optional((json) => json['checkOutTime'] = Timestamp.fromJson(json['checkOutTime']), defaultSerializer);
|
||||||
String roleId;
|
Optional<String> _roleId = Optional.optional(nativeFromJson, nativeToJson);
|
||||||
|
|
||||||
final FirebaseDataConnect _dataConnect; UpdateApplicationStatusVariablesBuilder shiftId(String? t) {
|
final FirebaseDataConnect _dataConnect; UpdateApplicationStatusVariablesBuilder shiftId(String? t) {
|
||||||
_shiftId.value = t;
|
_shiftId.value = t;
|
||||||
@@ -29,8 +29,12 @@ class UpdateApplicationStatusVariablesBuilder {
|
|||||||
_checkOutTime.value = t;
|
_checkOutTime.value = t;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
UpdateApplicationStatusVariablesBuilder roleId(String? t) {
|
||||||
|
_roleId.value = t;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
UpdateApplicationStatusVariablesBuilder(this._dataConnect, {required this.id,required this.roleId,});
|
UpdateApplicationStatusVariablesBuilder(this._dataConnect, {required this.id,});
|
||||||
Deserializer<UpdateApplicationStatusData> dataDeserializer = (dynamic json) => UpdateApplicationStatusData.fromJson(jsonDecode(json));
|
Deserializer<UpdateApplicationStatusData> dataDeserializer = (dynamic json) => UpdateApplicationStatusData.fromJson(jsonDecode(json));
|
||||||
Serializer<UpdateApplicationStatusVariables> varsSerializer = (UpdateApplicationStatusVariables vars) => jsonEncode(vars.toJson());
|
Serializer<UpdateApplicationStatusVariables> varsSerializer = (UpdateApplicationStatusVariables vars) => jsonEncode(vars.toJson());
|
||||||
Future<OperationResult<UpdateApplicationStatusData, UpdateApplicationStatusVariables>> execute() {
|
Future<OperationResult<UpdateApplicationStatusData, UpdateApplicationStatusVariables>> execute() {
|
||||||
@@ -38,7 +42,7 @@ class UpdateApplicationStatusVariablesBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MutationRef<UpdateApplicationStatusData, UpdateApplicationStatusVariables> ref() {
|
MutationRef<UpdateApplicationStatusData, UpdateApplicationStatusVariables> ref() {
|
||||||
UpdateApplicationStatusVariables vars= UpdateApplicationStatusVariables(id: id,shiftId: _shiftId,staffId: _staffId,status: _status,checkInTime: _checkInTime,checkOutTime: _checkOutTime,roleId: roleId,);
|
UpdateApplicationStatusVariables vars= UpdateApplicationStatusVariables(id: id,shiftId: _shiftId,staffId: _staffId,status: _status,checkInTime: _checkInTime,checkOutTime: _checkOutTime,roleId: _roleId,);
|
||||||
return _dataConnect.mutation("updateApplicationStatus", dataDeserializer, varsSerializer, vars);
|
return _dataConnect.mutation("updateApplicationStatus", dataDeserializer, varsSerializer, vars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,12 +125,11 @@ class UpdateApplicationStatusVariables {
|
|||||||
late final Optional<ApplicationStatus>status;
|
late final Optional<ApplicationStatus>status;
|
||||||
late final Optional<Timestamp>checkInTime;
|
late final Optional<Timestamp>checkInTime;
|
||||||
late final Optional<Timestamp>checkOutTime;
|
late final Optional<Timestamp>checkOutTime;
|
||||||
final String roleId;
|
late final Optional<String>roleId;
|
||||||
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
|
||||||
UpdateApplicationStatusVariables.fromJson(Map<String, dynamic> json):
|
UpdateApplicationStatusVariables.fromJson(Map<String, dynamic> json):
|
||||||
|
|
||||||
id = nativeFromJson<String>(json['id']),
|
id = nativeFromJson<String>(json['id']) {
|
||||||
roleId = nativeFromJson<String>(json['roleId']) {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -150,6 +153,9 @@ class UpdateApplicationStatusVariables {
|
|||||||
checkOutTime.value = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']);
|
checkOutTime.value = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']);
|
||||||
|
|
||||||
|
|
||||||
|
roleId = Optional.optional(nativeFromJson, nativeToJson);
|
||||||
|
roleId.value = json['roleId'] == null ? null : nativeFromJson<String>(json['roleId']);
|
||||||
|
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
@@ -192,7 +198,9 @@ class UpdateApplicationStatusVariables {
|
|||||||
if(checkOutTime.state == OptionalState.set) {
|
if(checkOutTime.state == OptionalState.set) {
|
||||||
json['checkOutTime'] = checkOutTime.toJson();
|
json['checkOutTime'] = checkOutTime.toJson();
|
||||||
}
|
}
|
||||||
json['roleId'] = nativeToJson<String>(roleId);
|
if(roleId.state == OptionalState.set) {
|
||||||
|
json['roleId'] = roleId.toJson();
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class ClockInAdapter {
|
|||||||
DateTime? checkInTime,
|
DateTime? checkInTime,
|
||||||
DateTime? checkOutTime,
|
DateTime? checkOutTime,
|
||||||
String? activeShiftId,
|
String? activeShiftId,
|
||||||
|
String? activeApplicationId,
|
||||||
}) {
|
}) {
|
||||||
final bool isCheckedIn = status == 'CHECKED_IN' || status == 'LATE'; // Assuming LATE is also checked in?
|
final bool isCheckedIn = status == 'CHECKED_IN' || status == 'LATE'; // Assuming LATE is also checked in?
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ class ClockInAdapter {
|
|||||||
checkInTime: checkInTime,
|
checkInTime: checkInTime,
|
||||||
checkOutTime: checkOutTime,
|
checkOutTime: checkOutTime,
|
||||||
activeShiftId: activeShiftId,
|
activeShiftId: activeShiftId,
|
||||||
|
activeApplicationId: activeApplicationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ class AttendanceStatus extends Equatable {
|
|||||||
final DateTime? checkInTime;
|
final DateTime? checkInTime;
|
||||||
final DateTime? checkOutTime;
|
final DateTime? checkOutTime;
|
||||||
final String? activeShiftId;
|
final String? activeShiftId;
|
||||||
|
final String? activeApplicationId;
|
||||||
|
|
||||||
const AttendanceStatus({
|
const AttendanceStatus({
|
||||||
this.isCheckedIn = false,
|
this.isCheckedIn = false,
|
||||||
this.checkInTime,
|
this.checkInTime,
|
||||||
this.checkOutTime,
|
this.checkOutTime,
|
||||||
this.activeShiftId,
|
this.activeShiftId,
|
||||||
|
this.activeApplicationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [isCheckedIn, checkInTime, checkOutTime, activeShiftId];
|
List<Object?> get props => [
|
||||||
|
isCheckedIn,
|
||||||
|
checkInTime,
|
||||||
|
checkOutTime,
|
||||||
|
activeShiftId,
|
||||||
|
activeApplicationId,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
|||||||
'Your Company';
|
'Your Company';
|
||||||
|
|
||||||
return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole) {
|
return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole) {
|
||||||
final DateTime? shiftDate = shiftRole.shift.date?.toDateTime();
|
final DateTime? shiftDate = shiftRole.shift.date?.toDateTime().toLocal();
|
||||||
final String dateStr = shiftDate == null
|
final String dateStr = shiftDate == null
|
||||||
? ''
|
? ''
|
||||||
: DateFormat('yyyy-MM-dd').format(shiftDate);
|
: DateFormat('yyyy-MM-dd').format(shiftDate);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '../../domain/repositories/clock_in_repository_interface.dart';
|
|||||||
class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
||||||
final dc.ExampleConnector _dataConnect;
|
final dc.ExampleConnector _dataConnect;
|
||||||
final Map<String, String> _shiftToApplicationId = {};
|
final Map<String, String> _shiftToApplicationId = {};
|
||||||
|
String? _activeApplicationId;
|
||||||
|
|
||||||
ClockInRepositoryImpl({
|
ClockInRepositoryImpl({
|
||||||
required dc.ExampleConnector dataConnect,
|
required dc.ExampleConnector dataConnect,
|
||||||
@@ -187,16 +188,34 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
|||||||
return const AttendanceStatus(isCheckedIn: false);
|
return const AttendanceStatus(isCheckedIn: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
final dc.GetApplicationsByStaffIdApplications? activeApp =
|
dc.GetApplicationsByStaffIdApplications? activeApp;
|
||||||
_getActiveApplication(apps);
|
for (final app in apps) {
|
||||||
final dc.GetApplicationsByStaffIdApplications app =
|
if (app.checkInTime != null && app.checkOutTime == null) {
|
||||||
activeApp ?? apps.last;
|
if (activeApp == null) {
|
||||||
|
activeApp = app;
|
||||||
|
} else {
|
||||||
|
final DateTime? current = _toDateTime(activeApp.checkInTime);
|
||||||
|
final DateTime? next = _toDateTime(app.checkInTime);
|
||||||
|
if (current == null || (next != null && next.isAfter(current))) {
|
||||||
|
activeApp = app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ClockInAdapter.toAttendanceStatus(
|
if (activeApp == null) {
|
||||||
status: app.status.stringValue,
|
_activeApplicationId = null;
|
||||||
checkInTime: _toDateTime(app.checkInTime),
|
return const AttendanceStatus(isCheckedIn: false);
|
||||||
checkOutTime: _toDateTime(app.checkOutTime),
|
}
|
||||||
activeShiftId: app.shiftId,
|
|
||||||
|
_activeApplicationId = activeApp.id;
|
||||||
|
print('Active check-in appId=$_activeApplicationId');
|
||||||
|
return AttendanceStatus(
|
||||||
|
isCheckedIn: true,
|
||||||
|
checkInTime: _toDateTime(activeApp.checkInTime),
|
||||||
|
checkOutTime: _toDateTime(activeApp.checkOutTime),
|
||||||
|
activeShiftId: activeApp.shiftId,
|
||||||
|
activeApplicationId: activeApp.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,34 +234,74 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
|||||||
app ??= (await _getTodaysApplications(staffId))
|
app ??= (await _getTodaysApplications(staffId))
|
||||||
.firstWhere((a) => a.shiftId == shiftId);
|
.firstWhere((a) => a.shiftId == shiftId);
|
||||||
|
|
||||||
await _dataConnect
|
final Timestamp checkInTs = _fromDateTime(DateTime.now());
|
||||||
.updateApplicationStatus(
|
print(
|
||||||
id: app.id,
|
'ClockIn request: appId=${app.id} shiftId=$shiftId '
|
||||||
roleId: app.shiftRole.id,
|
'checkInTime=${checkInTs.toJson()}',
|
||||||
)
|
);
|
||||||
.checkInTime(_fromDateTime(DateTime.now()))
|
try {
|
||||||
.execute();
|
await _dataConnect
|
||||||
|
.updateApplicationStatus(
|
||||||
|
id: app.id,
|
||||||
|
)
|
||||||
|
.checkInTime(checkInTs)
|
||||||
|
.execute();
|
||||||
|
_activeApplicationId = app.id;
|
||||||
|
} catch (e) {
|
||||||
|
print('ClockIn updateApplicationStatus error: $e');
|
||||||
|
print('ClockIn error type: ${e.runtimeType}');
|
||||||
|
try {
|
||||||
|
final dynamic err = e;
|
||||||
|
final dynamic details =
|
||||||
|
err.details ?? err.response ?? err.data ?? err.message;
|
||||||
|
if (details != null) {
|
||||||
|
print('ClockIn error details: $details');
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
return getAttendanceStatus();
|
return getAttendanceStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes}) async {
|
Future<AttendanceStatus> clockOut({
|
||||||
|
String? notes,
|
||||||
|
int? breakTimeMinutes,
|
||||||
|
String? applicationId,
|
||||||
|
}) async {
|
||||||
final String staffId = await _getStaffId();
|
final String staffId = await _getStaffId();
|
||||||
|
|
||||||
final List<dc.GetApplicationsByStaffIdApplications> apps =
|
print(
|
||||||
await _getTodaysApplications(staffId);
|
'ClockOut request: applicationId=$applicationId '
|
||||||
final dc.GetApplicationsByStaffIdApplications? app =
|
'activeApplicationId=$_activeApplicationId',
|
||||||
_getActiveApplication(apps);
|
);
|
||||||
if (app == null) throw Exception('No active shift found to clock out');
|
final String? targetAppId = applicationId ?? _activeApplicationId;
|
||||||
|
if (targetAppId == null || targetAppId.isEmpty) {
|
||||||
|
throw Exception('No active application id for checkout');
|
||||||
|
}
|
||||||
|
final appResult = await _dataConnect
|
||||||
|
.getApplicationById(id: targetAppId)
|
||||||
|
.execute();
|
||||||
|
final app = appResult.data.application;
|
||||||
|
print(
|
||||||
|
'ClockOut getApplicationById: id=${app?.id} '
|
||||||
|
'checkIn=${app?.checkInTime?.toJson()} '
|
||||||
|
'checkOut=${app?.checkOutTime?.toJson()}',
|
||||||
|
);
|
||||||
|
if (app == null) {
|
||||||
|
throw Exception('Application not found for checkout');
|
||||||
|
}
|
||||||
|
if (app.checkInTime == null || app.checkOutTime != null) {
|
||||||
|
throw Exception('No active shift found to clock out');
|
||||||
|
}
|
||||||
|
|
||||||
await _dataConnect.updateApplicationStatus(
|
await _dataConnect
|
||||||
id: app.id,
|
.updateApplicationStatus(
|
||||||
roleId: app.shiftRole.id,
|
id: targetAppId,
|
||||||
)
|
)
|
||||||
.status(dc.ApplicationStatus.CHECKED_OUT)
|
.checkOutTime(_fromDateTime(DateTime.now()))
|
||||||
.checkOutTime(_fromDateTime(DateTime.now()))
|
.execute();
|
||||||
.execute();
|
|
||||||
|
|
||||||
return getAttendanceStatus();
|
return getAttendanceStatus();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ class ClockOutArguments extends UseCaseArgument {
|
|||||||
/// Optional break time in minutes.
|
/// Optional break time in minutes.
|
||||||
final int? breakTimeMinutes;
|
final int? breakTimeMinutes;
|
||||||
|
|
||||||
|
/// Optional application id for checkout.
|
||||||
|
final String? applicationId;
|
||||||
|
|
||||||
/// Creates a [ClockOutArguments] instance.
|
/// Creates a [ClockOutArguments] instance.
|
||||||
const ClockOutArguments({
|
const ClockOutArguments({
|
||||||
this.notes,
|
this.notes,
|
||||||
this.breakTimeMinutes,
|
this.breakTimeMinutes,
|
||||||
|
this.applicationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [notes, breakTimeMinutes];
|
List<Object?> get props => [notes, breakTimeMinutes, applicationId];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,5 +17,9 @@ abstract class ClockInRepositoryInterface {
|
|||||||
|
|
||||||
/// Checks the user out for the currently active shift.
|
/// Checks the user out for the currently active shift.
|
||||||
/// Optionally accepts [breakTimeMinutes] if tracked.
|
/// Optionally accepts [breakTimeMinutes] if tracked.
|
||||||
Future<AttendanceStatus> clockOut({String? notes, int? breakTimeMinutes});
|
Future<AttendanceStatus> clockOut({
|
||||||
|
String? notes,
|
||||||
|
int? breakTimeMinutes,
|
||||||
|
String? applicationId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class ClockOutUseCase implements UseCase<ClockOutArguments, AttendanceStatus> {
|
|||||||
return _repository.clockOut(
|
return _repository.clockOut(
|
||||||
notes: arguments.notes,
|
notes: arguments.notes,
|
||||||
breakTimeMinutes: arguments.breakTimeMinutes,
|
breakTimeMinutes: arguments.breakTimeMinutes,
|
||||||
|
applicationId: arguments.applicationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
|
|||||||
ClockOutArguments(
|
ClockOutArguments(
|
||||||
notes: event.notes,
|
notes: event.notes,
|
||||||
breakTimeMinutes: 0, // Should be passed from event if supported
|
breakTimeMinutes: 0, // Should be passed from event if supported
|
||||||
|
applicationId: state.attendance.activeApplicationId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
return user.uid;
|
return user.uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime? _toDateTime(dynamic t) {
|
DateTime? _toDateTime(dynamic t, {String? debugKey}) {
|
||||||
if (t == null) return null;
|
if (t == null) return null;
|
||||||
DateTime? dt;
|
DateTime? dt;
|
||||||
if (t is Timestamp) {
|
if (t is Timestamp) {
|
||||||
@@ -72,60 +72,33 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dt != null) {
|
if (dt != null) {
|
||||||
return DateTimeUtils.toDeviceTime(dt);
|
final local = DateTimeUtils.toDeviceTime(dt);
|
||||||
|
if (debugKey != null && debugKey.isNotEmpty) {
|
||||||
|
print(
|
||||||
|
'ShiftDate convert: key=$debugKey raw=$t parsed=${dt.toIso8601String()} local=${local.toIso8601String()}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return local;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper method to map Data Connect application to domain Shift using ShiftAdapter.
|
|
||||||
Shift _mapApplicationToShift(
|
|
||||||
dynamic app,
|
|
||||||
String status, {
|
|
||||||
bool hasApplied = true,
|
|
||||||
}) {
|
|
||||||
return ShiftAdapter.fromApplicationData(
|
|
||||||
shiftId: app.shift.id,
|
|
||||||
roleId: app.shiftRole.roleId,
|
|
||||||
roleName: app.shiftRole.role.name,
|
|
||||||
businessName: app.shift.order.business.businessName,
|
|
||||||
companyLogoUrl: app.shift.order.business.companyLogoUrl,
|
|
||||||
costPerHour: app.shiftRole.role.costPerHour,
|
|
||||||
shiftLocation: app.shift.location,
|
|
||||||
teamHubName: app.shift.order.teamHub.hubName,
|
|
||||||
shiftDate: _toDateTime(app.shift.date),
|
|
||||||
startTime: _toDateTime(app.shiftRole.startTime),
|
|
||||||
endTime: _toDateTime(app.shiftRole.endTime),
|
|
||||||
createdAt: _toDateTime(app.createdAt),
|
|
||||||
status: status,
|
|
||||||
description: app.shift.description,
|
|
||||||
durationDays: app.shift.durationDays,
|
|
||||||
count: app.shiftRole.count,
|
|
||||||
assigned: app.shiftRole.assigned,
|
|
||||||
eventName: app.shift.order.eventName,
|
|
||||||
hasApplied: hasApplied,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getMyShifts({
|
Future<List<Shift>> getMyShifts({
|
||||||
required DateTime start,
|
required DateTime start,
|
||||||
required DateTime end,
|
required DateTime end,
|
||||||
}) async {
|
}) async {
|
||||||
return _fetchApplications(
|
return _fetchApplications(start: start, end: end);
|
||||||
[dc.ApplicationStatus.ACCEPTED, dc.ApplicationStatus.CONFIRMED],
|
|
||||||
start: start,
|
|
||||||
end: end,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getPendingAssignments() async {
|
Future<List<Shift>> getPendingAssignments() async {
|
||||||
return _fetchApplications([dc.ApplicationStatus.PENDING]);
|
return <Shift>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Shift>> getCancelledShifts() async {
|
Future<List<Shift>> getCancelledShifts() async {
|
||||||
return _fetchApplications([dc.ApplicationStatus.REJECTED]);
|
return <Shift>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -141,10 +114,37 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||||
|
|
||||||
|
final String roleName = app.shiftRole.role.name;
|
||||||
|
final String orderName =
|
||||||
|
(app.shift.order.eventName ?? '').trim().isNotEmpty
|
||||||
|
? app.shift.order.eventName!
|
||||||
|
: app.shift.order.business.businessName;
|
||||||
|
final String title = '$roleName - $orderName';
|
||||||
|
final DateTime? shiftDate = _toDateTime(app.shift.date);
|
||||||
|
final DateTime? startDt = _toDateTime(app.shiftRole.startTime);
|
||||||
|
final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
|
||||||
|
final DateTime? createdDt = _toDateTime(app.createdAt);
|
||||||
|
|
||||||
shifts.add(
|
shifts.add(
|
||||||
_mapApplicationToShift(
|
Shift(
|
||||||
app,
|
id: app.shift.id,
|
||||||
_mapStatus(dc.ApplicationStatus.CHECKED_OUT),
|
roleId: app.shiftRole.roleId,
|
||||||
|
title: title,
|
||||||
|
clientName: app.shift.order.business.businessName,
|
||||||
|
logoUrl: app.shift.order.business.companyLogoUrl,
|
||||||
|
hourlyRate: app.shiftRole.role.costPerHour,
|
||||||
|
location: app.shift.location ?? '',
|
||||||
|
locationAddress: app.shift.order.teamHub.hubName,
|
||||||
|
date: shiftDate?.toIso8601String() ?? '',
|
||||||
|
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||||
|
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||||
|
createdDate: createdDt?.toIso8601String() ?? '',
|
||||||
|
status: _mapStatus(dc.ApplicationStatus.CHECKED_OUT),
|
||||||
|
description: app.shift.description,
|
||||||
|
durationDays: app.shift.durationDays,
|
||||||
|
requiredSlots: app.shiftRole.count,
|
||||||
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
|
hasApplied: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,8 +154,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Shift>> _fetchApplications(
|
Future<List<Shift>> _fetchApplications({
|
||||||
List<dc.ApplicationStatus> statuses, {
|
|
||||||
DateTime? start,
|
DateTime? start,
|
||||||
DateTime? end,
|
DateTime? end,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -169,22 +168,58 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
final response = await query.execute();
|
final response = await query.execute();
|
||||||
|
|
||||||
final statusNames = statuses.map((s) => s.name).toSet();
|
final apps = response.data.applications;
|
||||||
final apps = response.data.applications.where(
|
|
||||||
(app) => statusNames.contains(app.status.stringValue),
|
|
||||||
);
|
|
||||||
final List<Shift> shifts = [];
|
final List<Shift> shifts = [];
|
||||||
|
|
||||||
for (final app in apps) {
|
for (final app in apps) {
|
||||||
_shiftToAppIdMap[app.shift.id] = app.id;
|
_shiftToAppIdMap[app.shift.id] = app.id;
|
||||||
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
_appToRoleIdMap[app.id] = app.shiftRole.id;
|
||||||
|
|
||||||
// Use the first matching status for mapping
|
final String roleName = app.shiftRole.role.name;
|
||||||
final matchingStatus = statuses.firstWhere(
|
final String orderName =
|
||||||
(s) => s.name == app.status.stringValue,
|
(app.shift.order.eventName ?? '').trim().isNotEmpty
|
||||||
orElse: () => statuses.first,
|
? app.shift.order.eventName!
|
||||||
|
: app.shift.order.business.businessName;
|
||||||
|
final String title = '$roleName - $orderName';
|
||||||
|
final DateTime? shiftDate = _toDateTime(app.shift.date);
|
||||||
|
final DateTime? startDt = _toDateTime(app.shiftRole.startTime);
|
||||||
|
final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
|
||||||
|
final DateTime? createdDt = _toDateTime(app.createdAt);
|
||||||
|
|
||||||
|
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED)
|
||||||
|
final bool hasCheckIn = app.checkInTime != null;
|
||||||
|
final bool hasCheckOut = app.checkOutTime != null;
|
||||||
|
dc.ApplicationStatus? appStatus;
|
||||||
|
if (app.status is dc.Known<dc.ApplicationStatus>) {
|
||||||
|
appStatus = (app.status as dc.Known<dc.ApplicationStatus>).value;
|
||||||
|
}
|
||||||
|
final String mappedStatus = hasCheckOut
|
||||||
|
? 'completed'
|
||||||
|
: hasCheckIn
|
||||||
|
? 'checked_in'
|
||||||
|
: _mapStatus(appStatus ?? dc.ApplicationStatus.ACCEPTED);
|
||||||
|
shifts.add(
|
||||||
|
Shift(
|
||||||
|
id: app.shift.id,
|
||||||
|
roleId: app.shiftRole.roleId,
|
||||||
|
title: title,
|
||||||
|
clientName: app.shift.order.business.businessName,
|
||||||
|
logoUrl: app.shift.order.business.companyLogoUrl,
|
||||||
|
hourlyRate: app.shiftRole.role.costPerHour,
|
||||||
|
location: app.shift.location ?? '',
|
||||||
|
locationAddress: app.shift.order.teamHub.hubName,
|
||||||
|
date: shiftDate?.toIso8601String() ?? '',
|
||||||
|
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
|
||||||
|
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
|
||||||
|
createdDate: createdDt?.toIso8601String() ?? '',
|
||||||
|
status: mappedStatus,
|
||||||
|
description: app.shift.description,
|
||||||
|
durationDays: app.shift.durationDays,
|
||||||
|
requiredSlots: app.shiftRole.count,
|
||||||
|
filledSlots: app.shiftRole.assigned ?? 0,
|
||||||
|
hasApplied: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
shifts.add(_mapApplicationToShift(app, _mapStatus(matchingStatus)));
|
|
||||||
}
|
}
|
||||||
return shifts;
|
return shifts;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -230,10 +265,22 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
|
|
||||||
final List<Shift> mappedShifts = [];
|
final List<Shift> mappedShifts = [];
|
||||||
for (final sr in allShiftRoles) {
|
for (final sr in allShiftRoles) {
|
||||||
|
print(
|
||||||
|
'FindShifts raw: shiftId=${sr.shiftId} roleId=${sr.roleId} '
|
||||||
|
'start=${sr.startTime?.toJson()} end=${sr.endTime?.toJson()} '
|
||||||
|
'shiftDate=${sr.shift.date?.toJson()}',
|
||||||
|
);
|
||||||
final DateTime? shiftDate = _toDateTime(sr.shift.date);
|
final DateTime? shiftDate = _toDateTime(sr.shift.date);
|
||||||
final startDt = _toDateTime(sr.startTime);
|
final startDt = _toDateTime(sr.startTime);
|
||||||
final endDt = _toDateTime(sr.endTime);
|
final endDt = _toDateTime(sr.endTime);
|
||||||
final createdDt = _toDateTime(sr.createdAt);
|
final createdDt = _toDateTime(sr.createdAt);
|
||||||
|
print(
|
||||||
|
'FindShifts mapped: shiftId=${sr.shiftId} '
|
||||||
|
'origStart=${sr.startTime?.toJson()} '
|
||||||
|
'origEnd=${sr.endTime?.toJson()} '
|
||||||
|
'mappedStart=${startDt != null ? DateFormat('HH:mm').format(startDt) : ''} '
|
||||||
|
'mappedEnd=${endDt != null ? DateFormat('HH:mm').format(endDt) : ''}',
|
||||||
|
);
|
||||||
mappedShifts.add(
|
mappedShifts.add(
|
||||||
Shift(
|
Shift(
|
||||||
id: sr.shiftId,
|
id: sr.shiftId,
|
||||||
@@ -293,7 +340,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
final DateTime? endDt = _toDateTime(sr.endTime);
|
final DateTime? endDt = _toDateTime(sr.endTime);
|
||||||
final DateTime? createdDt = _toDateTime(sr.createdAt);
|
final DateTime? createdDt = _toDateTime(sr.createdAt);
|
||||||
|
|
||||||
final String? staffId = _auth.currentUser?.uid;
|
final String? staffId = await _getStaffId();
|
||||||
bool hasApplied = false;
|
bool hasApplied = false;
|
||||||
String status = 'open';
|
String status = 'open';
|
||||||
if (staffId != null) {
|
if (staffId != null) {
|
||||||
@@ -466,7 +513,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
shiftId: shiftId,
|
shiftId: shiftId,
|
||||||
staffId: staffId,
|
staffId: staffId,
|
||||||
roleId: targetRoleId,
|
roleId: targetRoleId,
|
||||||
status: dc.ApplicationStatus.CONFIRMED,
|
status: dc.ApplicationStatus.ACCEPTED,
|
||||||
origin: dc.ApplicationOrigin.STAFF,
|
origin: dc.ApplicationOrigin.STAFF,
|
||||||
)
|
)
|
||||||
// TODO: this should be PENDING so a vendor can accept it.
|
// TODO: this should be PENDING so a vendor can accept it.
|
||||||
@@ -500,7 +547,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> acceptShift(String shiftId) async {
|
Future<void> acceptShift(String shiftId) async {
|
||||||
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED);
|
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -563,7 +610,7 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _dataConnect
|
await _dataConnect
|
||||||
.updateApplicationStatus(id: appId, roleId: roleId)
|
.updateApplicationStatus(id: appId)
|
||||||
.status(newStatus)
|
.status(newStatus)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
}) : super(ShiftsInitial()) {
|
}) : super(ShiftsInitial()) {
|
||||||
on<LoadShiftsEvent>(_onLoadShifts);
|
on<LoadShiftsEvent>(_onLoadShifts);
|
||||||
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
|
on<LoadHistoryShiftsEvent>(_onLoadHistoryShifts);
|
||||||
|
on<LoadAvailableShiftsEvent>(_onLoadAvailableShifts);
|
||||||
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
|
on<LoadShiftsForRangeEvent>(_onLoadShiftsForRange);
|
||||||
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
on<FilterAvailableShiftsEvent>(_onFilterAvailableShifts);
|
||||||
}
|
}
|
||||||
@@ -50,18 +51,15 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
final myShiftsResult = await getMyShifts(
|
final myShiftsResult = await getMyShifts(
|
||||||
GetMyShiftsArguments(start: days.first, end: days.last),
|
GetMyShiftsArguments(start: days.first, end: days.last),
|
||||||
);
|
);
|
||||||
final pendingResult = await getPendingAssignments();
|
|
||||||
final cancelledResult = await getCancelledShifts();
|
|
||||||
|
|
||||||
// Initial available with defaults
|
|
||||||
final availableResult = await getAvailableShifts(const GetAvailableShiftsArguments());
|
|
||||||
|
|
||||||
emit(ShiftsLoaded(
|
emit(ShiftsLoaded(
|
||||||
myShifts: myShiftsResult,
|
myShifts: myShiftsResult,
|
||||||
pendingShifts: pendingResult,
|
pendingShifts: const [],
|
||||||
cancelledShifts: cancelledResult,
|
cancelledShifts: const [],
|
||||||
availableShifts: _filterPastShifts(availableResult),
|
availableShifts: const [],
|
||||||
historyShifts: const [],
|
historyShifts: const [],
|
||||||
|
availableLoading: false,
|
||||||
|
availableLoaded: false,
|
||||||
historyLoading: false,
|
historyLoading: false,
|
||||||
historyLoaded: false,
|
historyLoaded: false,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
@@ -93,6 +91,28 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadAvailableShifts(
|
||||||
|
LoadAvailableShiftsEvent event,
|
||||||
|
Emitter<ShiftsState> emit,
|
||||||
|
) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is! ShiftsLoaded) return;
|
||||||
|
if (currentState.availableLoading || currentState.availableLoaded) return;
|
||||||
|
|
||||||
|
emit(currentState.copyWith(availableLoading: true));
|
||||||
|
try {
|
||||||
|
final availableResult =
|
||||||
|
await getAvailableShifts(const GetAvailableShiftsArguments());
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
availableShifts: _filterPastShifts(availableResult),
|
||||||
|
availableLoading: false,
|
||||||
|
availableLoaded: true,
|
||||||
|
));
|
||||||
|
} catch (_) {
|
||||||
|
emit(currentState.copyWith(availableLoading: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onLoadShiftsForRange(
|
Future<void> _onLoadShiftsForRange(
|
||||||
LoadShiftsForRangeEvent event,
|
LoadShiftsForRangeEvent event,
|
||||||
Emitter<ShiftsState> emit,
|
Emitter<ShiftsState> emit,
|
||||||
@@ -108,17 +128,14 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final pendingResult = await getPendingAssignments();
|
|
||||||
final cancelledResult = await getCancelledShifts();
|
|
||||||
final availableResult =
|
|
||||||
await getAvailableShifts(const GetAvailableShiftsArguments());
|
|
||||||
|
|
||||||
emit(ShiftsLoaded(
|
emit(ShiftsLoaded(
|
||||||
myShifts: myShiftsResult,
|
myShifts: myShiftsResult,
|
||||||
pendingShifts: pendingResult,
|
pendingShifts: const [],
|
||||||
cancelledShifts: cancelledResult,
|
cancelledShifts: const [],
|
||||||
availableShifts: _filterPastShifts(availableResult),
|
availableShifts: const [],
|
||||||
historyShifts: const [],
|
historyShifts: const [],
|
||||||
|
availableLoading: false,
|
||||||
|
availableLoaded: false,
|
||||||
historyLoading: false,
|
historyLoading: false,
|
||||||
historyLoaded: false,
|
historyLoaded: false,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
@@ -135,6 +152,10 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState> {
|
|||||||
) async {
|
) async {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is ShiftsLoaded) {
|
if (currentState is ShiftsLoaded) {
|
||||||
|
if (!currentState.availableLoaded && !currentState.availableLoading) {
|
||||||
|
add(LoadAvailableShiftsEvent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Optimistic update or loading indicator?
|
// Optimistic update or loading indicator?
|
||||||
// Since it's filtering, we can just reload available.
|
// Since it's filtering, we can just reload available.
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class LoadShiftsEvent extends ShiftsEvent {}
|
|||||||
|
|
||||||
class LoadHistoryShiftsEvent extends ShiftsEvent {}
|
class LoadHistoryShiftsEvent extends ShiftsEvent {}
|
||||||
|
|
||||||
|
class LoadAvailableShiftsEvent extends ShiftsEvent {}
|
||||||
|
|
||||||
class LoadShiftsForRangeEvent extends ShiftsEvent {
|
class LoadShiftsForRangeEvent extends ShiftsEvent {
|
||||||
final DateTime start;
|
final DateTime start;
|
||||||
final DateTime end;
|
final DateTime end;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class ShiftsLoaded extends ShiftsState {
|
|||||||
final List<Shift> cancelledShifts;
|
final List<Shift> cancelledShifts;
|
||||||
final List<Shift> availableShifts;
|
final List<Shift> availableShifts;
|
||||||
final List<Shift> historyShifts;
|
final List<Shift> historyShifts;
|
||||||
|
final bool availableLoading;
|
||||||
|
final bool availableLoaded;
|
||||||
final bool historyLoading;
|
final bool historyLoading;
|
||||||
final bool historyLoaded;
|
final bool historyLoaded;
|
||||||
final String searchQuery;
|
final String searchQuery;
|
||||||
@@ -29,6 +31,8 @@ class ShiftsLoaded extends ShiftsState {
|
|||||||
required this.cancelledShifts,
|
required this.cancelledShifts,
|
||||||
required this.availableShifts,
|
required this.availableShifts,
|
||||||
required this.historyShifts,
|
required this.historyShifts,
|
||||||
|
required this.availableLoading,
|
||||||
|
required this.availableLoaded,
|
||||||
required this.historyLoading,
|
required this.historyLoading,
|
||||||
required this.historyLoaded,
|
required this.historyLoaded,
|
||||||
required this.searchQuery,
|
required this.searchQuery,
|
||||||
@@ -41,6 +45,8 @@ class ShiftsLoaded extends ShiftsState {
|
|||||||
List<Shift>? cancelledShifts,
|
List<Shift>? cancelledShifts,
|
||||||
List<Shift>? availableShifts,
|
List<Shift>? availableShifts,
|
||||||
List<Shift>? historyShifts,
|
List<Shift>? historyShifts,
|
||||||
|
bool? availableLoading,
|
||||||
|
bool? availableLoaded,
|
||||||
bool? historyLoading,
|
bool? historyLoading,
|
||||||
bool? historyLoaded,
|
bool? historyLoaded,
|
||||||
String? searchQuery,
|
String? searchQuery,
|
||||||
@@ -52,6 +58,8 @@ class ShiftsLoaded extends ShiftsState {
|
|||||||
cancelledShifts: cancelledShifts ?? this.cancelledShifts,
|
cancelledShifts: cancelledShifts ?? this.cancelledShifts,
|
||||||
availableShifts: availableShifts ?? this.availableShifts,
|
availableShifts: availableShifts ?? this.availableShifts,
|
||||||
historyShifts: historyShifts ?? this.historyShifts,
|
historyShifts: historyShifts ?? this.historyShifts,
|
||||||
|
availableLoading: availableLoading ?? this.availableLoading,
|
||||||
|
availableLoaded: availableLoaded ?? this.availableLoaded,
|
||||||
historyLoading: historyLoading ?? this.historyLoading,
|
historyLoading: historyLoading ?? this.historyLoading,
|
||||||
historyLoaded: historyLoaded ?? this.historyLoaded,
|
historyLoaded: historyLoaded ?? this.historyLoaded,
|
||||||
searchQuery: searchQuery ?? this.searchQuery,
|
searchQuery: searchQuery ?? this.searchQuery,
|
||||||
@@ -66,6 +74,8 @@ class ShiftsLoaded extends ShiftsState {
|
|||||||
cancelledShifts,
|
cancelledShifts,
|
||||||
availableShifts,
|
availableShifts,
|
||||||
historyShifts,
|
historyShifts,
|
||||||
|
availableLoading,
|
||||||
|
availableLoaded,
|
||||||
historyLoading,
|
historyLoading,
|
||||||
historyLoaded,
|
historyLoaded,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
if (_activeTab == 'history') {
|
if (_activeTab == 'history') {
|
||||||
_bloc.add(LoadHistoryShiftsEvent());
|
_bloc.add(LoadHistoryShiftsEvent());
|
||||||
}
|
}
|
||||||
|
if (_activeTab == 'find') {
|
||||||
|
_bloc.add(LoadAvailableShiftsEvent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -55,12 +58,19 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
value: _bloc,
|
value: _bloc,
|
||||||
child: BlocBuilder<ShiftsBloc, ShiftsState>(
|
child: BlocBuilder<ShiftsBloc, ShiftsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final bool baseLoaded = state is ShiftsLoaded;
|
||||||
final List<Shift> myShifts = (state is ShiftsLoaded)
|
final List<Shift> myShifts = (state is ShiftsLoaded)
|
||||||
? state.myShifts
|
? state.myShifts
|
||||||
: [];
|
: [];
|
||||||
final List<Shift> availableJobs = (state is ShiftsLoaded)
|
final List<Shift> availableJobs = (state is ShiftsLoaded)
|
||||||
? state.availableShifts
|
? state.availableShifts
|
||||||
: [];
|
: [];
|
||||||
|
final bool availableLoading = (state is ShiftsLoaded)
|
||||||
|
? state.availableLoading
|
||||||
|
: false;
|
||||||
|
final bool availableLoaded = (state is ShiftsLoaded)
|
||||||
|
? state.availableLoaded
|
||||||
|
: false;
|
||||||
final List<Shift> pendingAssignments = (state is ShiftsLoaded)
|
final List<Shift> pendingAssignments = (state is ShiftsLoaded)
|
||||||
? state.pendingShifts
|
? state.pendingShifts
|
||||||
: [];
|
: [];
|
||||||
@@ -114,6 +124,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
"My Shifts",
|
"My Shifts",
|
||||||
UiIcons.calendar,
|
UiIcons.calendar,
|
||||||
myShifts.length,
|
myShifts.length,
|
||||||
|
enabled: true,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildTab(
|
_buildTab(
|
||||||
@@ -122,6 +133,8 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
UiIcons.search,
|
UiIcons.search,
|
||||||
availableJobs
|
availableJobs
|
||||||
.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs.
|
.length, // Passed unfiltered count as badge? Or logic inside? Pass availableJobs.
|
||||||
|
showCount: availableLoaded,
|
||||||
|
enabled: baseLoaded,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildTab(
|
_buildTab(
|
||||||
@@ -130,6 +143,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
UiIcons.clock,
|
UiIcons.clock,
|
||||||
historyShifts.length,
|
historyShifts.length,
|
||||||
showCount: historyLoaded,
|
showCount: historyLoaded,
|
||||||
|
enabled: baseLoaded,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -147,6 +161,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
cancelledShifts,
|
cancelledShifts,
|
||||||
availableJobs,
|
availableJobs,
|
||||||
historyShifts,
|
historyShifts,
|
||||||
|
availableLoading,
|
||||||
historyLoading,
|
historyLoading,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -164,6 +179,7 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
List<Shift> cancelledShifts,
|
List<Shift> cancelledShifts,
|
||||||
List<Shift> availableJobs,
|
List<Shift> availableJobs,
|
||||||
List<Shift> historyShifts,
|
List<Shift> historyShifts,
|
||||||
|
bool availableLoading,
|
||||||
bool historyLoading,
|
bool historyLoading,
|
||||||
) {
|
) {
|
||||||
switch (_activeTab) {
|
switch (_activeTab) {
|
||||||
@@ -175,6 +191,9 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
initialDate: _selectedDate,
|
initialDate: _selectedDate,
|
||||||
);
|
);
|
||||||
case 'find':
|
case 'find':
|
||||||
|
if (availableLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
return FindShiftsTab(availableJobs: availableJobs);
|
return FindShiftsTab(availableJobs: availableJobs);
|
||||||
case 'history':
|
case 'history':
|
||||||
if (historyLoading) {
|
if (historyLoading) {
|
||||||
@@ -192,15 +211,21 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
IconData icon,
|
IconData icon,
|
||||||
int count, {
|
int count, {
|
||||||
bool showCount = true,
|
bool showCount = true,
|
||||||
|
bool enabled = true,
|
||||||
}) {
|
}) {
|
||||||
final isActive = _activeTab == id;
|
final isActive = _activeTab == id;
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: !enabled
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
setState(() => _activeTab = id);
|
setState(() => _activeTab = id);
|
||||||
if (id == 'history') {
|
if (id == 'history') {
|
||||||
_bloc.add(LoadHistoryShiftsEvent());
|
_bloc.add(LoadHistoryShiftsEvent());
|
||||||
}
|
}
|
||||||
|
if (id == 'find') {
|
||||||
|
_bloc.add(LoadAvailableShiftsEvent());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||||
@@ -217,7 +242,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: isActive ? AppColors.krowBlue : Colors.white,
|
color: !enabled
|
||||||
|
? Colors.white.withAlpha((0.5 * 255).round())
|
||||||
|
: isActive
|
||||||
|
? AppColors.krowBlue
|
||||||
|
: Colors.white,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Flexible(
|
Flexible(
|
||||||
@@ -226,7 +255,11 @@ class _ShiftsPageState extends State<ShiftsPage> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: isActive ? AppColors.krowBlue : Colors.white,
|
color: !enabled
|
||||||
|
? Colors.white.withAlpha((0.5 * 255).round())
|
||||||
|
: isActive
|
||||||
|
? AppColors.krowBlue
|
||||||
|
: Colors.white,
|
||||||
),
|
),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
|||||||
statusText = t.staff_shifts.status.confirmed;
|
statusText = t.staff_shifts.status.confirmed;
|
||||||
statusColor = UiColors.textLink;
|
statusColor = UiColors.textLink;
|
||||||
statusBg = UiColors.primary;
|
statusBg = UiColors.primary;
|
||||||
|
} else if (status == 'checked_in') {
|
||||||
|
statusText = 'Checked in';
|
||||||
|
statusColor = UiColors.textSuccess;
|
||||||
|
statusBg = UiColors.iconSuccess;
|
||||||
} else if (status == 'pending' || status == 'open') {
|
} else if (status == 'pending' || status == 'open') {
|
||||||
statusText = t.staff_shifts.status.act_now;
|
statusText = t.staff_shifts.status.act_now;
|
||||||
statusColor = UiColors.destructive;
|
statusColor = UiColors.destructive;
|
||||||
|
|||||||
@@ -170,8 +170,21 @@ class _FindShiftsTabState extends State<FindShiftsTab> {
|
|||||||
...filteredJobs.map(
|
...filteredJobs.map(
|
||||||
(shift) => Padding(
|
(shift) => Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
child: MyShiftCard(
|
child: Column(
|
||||||
shift: shift,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Debug shiftId: ${shift.id}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
MyShiftCard(
|
||||||
|
shift: shift,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ mutation updateApplicationStatus(
|
|||||||
$status: ApplicationStatus
|
$status: ApplicationStatus
|
||||||
$checkInTime: Timestamp
|
$checkInTime: Timestamp
|
||||||
$checkOutTime: Timestamp
|
$checkOutTime: Timestamp
|
||||||
$roleId: UUID!
|
$roleId: UUID
|
||||||
) @auth(level: USER) {
|
) @auth(level: USER) {
|
||||||
application_update(
|
application_update(
|
||||||
id: $id
|
id: $id
|
||||||
|
|||||||
Reference in New Issue
Block a user