This commit is contained in:
José Salazar
2026-02-01 22:39:40 +09:00
parent 3cebb37dfd
commit 6277b9f5e2
19 changed files with 20900 additions and 18729 deletions

View File

@@ -1,16 +1,16 @@
# Basic Usage # Basic Usage
```dart ```dart
ExampleConnector.instance.createRecentPayment(createRecentPaymentVariables).execute(); ExampleConnector.instance.createHub(createHubVariables).execute();
ExampleConnector.instance.updateRecentPayment(updateRecentPaymentVariables).execute(); ExampleConnector.instance.updateHub(updateHubVariables).execute();
ExampleConnector.instance.deleteRecentPayment(deleteRecentPaymentVariables).execute(); ExampleConnector.instance.deleteHub(deleteHubVariables).execute();
ExampleConnector.instance.CreateStaff(createStaffVariables).execute(); ExampleConnector.instance.listInvoices(listInvoicesVariables).execute();
ExampleConnector.instance.UpdateStaff(updateStaffVariables).execute(); ExampleConnector.instance.getInvoiceById(getInvoiceByIdVariables).execute();
ExampleConnector.instance.DeleteStaff(deleteStaffVariables).execute(); ExampleConnector.instance.listInvoicesByVendorId(listInvoicesByVendorIdVariables).execute();
ExampleConnector.instance.getStaffDocumentByKey(getStaffDocumentByKeyVariables).execute(); ExampleConnector.instance.listInvoicesByBusinessId(listInvoicesByBusinessIdVariables).execute();
ExampleConnector.instance.listStaffDocumentsByStaffId(listStaffDocumentsByStaffIdVariables).execute(); ExampleConnector.instance.listInvoicesByOrderId(listInvoicesByOrderIdVariables).execute();
ExampleConnector.instance.listStaffDocumentsByDocumentType(listStaffDocumentsByDocumentTypeVariables).execute(); ExampleConnector.instance.listInvoicesByStatus(listInvoicesByStatusVariables).execute();
ExampleConnector.instance.listStaffDocumentsByStatus(listStaffDocumentsByStatusVariables).execute(); ExampleConnector.instance.filterInvoices(filterInvoicesVariables).execute();
``` ```
@@ -23,8 +23,8 @@ 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.updateAttireOption({ ... }) await ExampleConnector.instance.UpdateUser({ ... })
.itemId(...) .email(...)
.execute(); .execute();
``` ```

View File

@@ -253,13 +253,15 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
final String? notes; final String? notes;
final GetShiftRoleByIdShiftRoleShiftOrderBusiness business; final GetShiftRoleByIdShiftRoleShiftOrderBusiness business;
final GetShiftRoleByIdShiftRoleShiftOrderVendor? vendor; final GetShiftRoleByIdShiftRoleShiftOrderVendor? vendor;
final GetShiftRoleByIdShiftRoleShiftOrderTeamHub teamHub;
GetShiftRoleByIdShiftRoleShiftOrder.fromJson(dynamic json): GetShiftRoleByIdShiftRoleShiftOrder.fromJson(dynamic json):
recurringDays = json['recurringDays'] == null ? null : AnyValue.fromJson(json['recurringDays']), recurringDays = json['recurringDays'] == null ? null : AnyValue.fromJson(json['recurringDays']),
permanentDays = json['permanentDays'] == null ? null : AnyValue.fromJson(json['permanentDays']), permanentDays = json['permanentDays'] == null ? null : AnyValue.fromJson(json['permanentDays']),
notes = json['notes'] == null ? null : nativeFromJson<String>(json['notes']), notes = json['notes'] == null ? null : nativeFromJson<String>(json['notes']),
business = GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(json['business']), business = GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(json['business']),
vendor = json['vendor'] == null ? null : GetShiftRoleByIdShiftRoleShiftOrderVendor.fromJson(json['vendor']); vendor = json['vendor'] == null ? null : GetShiftRoleByIdShiftRoleShiftOrderVendor.fromJson(json['vendor']),
teamHub = GetShiftRoleByIdShiftRoleShiftOrderTeamHub.fromJson(json['teamHub']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -274,11 +276,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
permanentDays == otherTyped.permanentDays && permanentDays == otherTyped.permanentDays &&
notes == otherTyped.notes && notes == otherTyped.notes &&
business == otherTyped.business && business == otherTyped.business &&
vendor == otherTyped.vendor; vendor == otherTyped.vendor &&
teamHub == otherTyped.teamHub;
} }
@override @override
int get hashCode => Object.hashAll([recurringDays.hashCode, permanentDays.hashCode, notes.hashCode, business.hashCode, vendor.hashCode]); int get hashCode => Object.hashAll([recurringDays.hashCode, permanentDays.hashCode, notes.hashCode, business.hashCode, vendor.hashCode, teamHub.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -296,6 +299,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
if (vendor != null) { if (vendor != null) {
json['vendor'] = vendor!.toJson(); json['vendor'] = vendor!.toJson();
} }
json['teamHub'] = teamHub.toJson();
return json; return json;
} }
@@ -305,6 +309,7 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
this.notes, this.notes,
required this.business, required this.business,
this.vendor, this.vendor,
required this.teamHub,
}); });
} }
@@ -312,10 +317,12 @@ class GetShiftRoleByIdShiftRoleShiftOrder {
class GetShiftRoleByIdShiftRoleShiftOrderBusiness { class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
final String id; final String id;
final String businessName; final String businessName;
final String? companyLogoUrl;
GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(dynamic json): GetShiftRoleByIdShiftRoleShiftOrderBusiness.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']), id = nativeFromJson<String>(json['id']),
businessName = nativeFromJson<String>(json['businessName']); businessName = nativeFromJson<String>(json['businessName']),
companyLogoUrl = json['companyLogoUrl'] == null ? null : nativeFromJson<String>(json['companyLogoUrl']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -327,23 +334,28 @@ class GetShiftRoleByIdShiftRoleShiftOrderBusiness {
final GetShiftRoleByIdShiftRoleShiftOrderBusiness otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderBusiness; final GetShiftRoleByIdShiftRoleShiftOrderBusiness otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderBusiness;
return id == otherTyped.id && return id == otherTyped.id &&
businessName == otherTyped.businessName; businessName == otherTyped.businessName &&
companyLogoUrl == otherTyped.companyLogoUrl;
} }
@override @override
int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode]); int get hashCode => Object.hashAll([id.hashCode, businessName.hashCode, companyLogoUrl.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
Map<String, dynamic> json = {}; Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id); json['id'] = nativeToJson<String>(id);
json['businessName'] = nativeToJson<String>(businessName); json['businessName'] = nativeToJson<String>(businessName);
if (companyLogoUrl != null) {
json['companyLogoUrl'] = nativeToJson<String?>(companyLogoUrl);
}
return json; return json;
} }
GetShiftRoleByIdShiftRoleShiftOrderBusiness({ GetShiftRoleByIdShiftRoleShiftOrderBusiness({
required this.id, required this.id,
required this.businessName, required this.businessName,
this.companyLogoUrl,
}); });
} }
@@ -386,6 +398,40 @@ class GetShiftRoleByIdShiftRoleShiftOrderVendor {
}); });
} }
@immutable
class GetShiftRoleByIdShiftRoleShiftOrderTeamHub {
final String hubName;
GetShiftRoleByIdShiftRoleShiftOrderTeamHub.fromJson(dynamic json):
hubName = nativeFromJson<String>(json['hubName']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final GetShiftRoleByIdShiftRoleShiftOrderTeamHub otherTyped = other as GetShiftRoleByIdShiftRoleShiftOrderTeamHub;
return hubName == otherTyped.hubName;
}
@override
int get hashCode => hubName.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['hubName'] = nativeToJson<String>(hubName);
return json;
}
GetShiftRoleByIdShiftRoleShiftOrderTeamHub({
required this.hubName,
});
}
@immutable @immutable
class GetShiftRoleByIdData { class GetShiftRoleByIdData {
final GetShiftRoleByIdShiftRole? shiftRole; final GetShiftRoleByIdShiftRole? shiftRole;

View File

@@ -204,6 +204,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
final String? locationAddress; final String? locationAddress;
final String? description; final String? description;
final String orderId; final String orderId;
final EnumValue<ShiftStatus>? status;
final int? durationDays;
final ListShiftRolesByVendorIdShiftRolesShiftOrder order; final ListShiftRolesByVendorIdShiftRolesShiftOrder order;
ListShiftRolesByVendorIdShiftRolesShift.fromJson(dynamic json): ListShiftRolesByVendorIdShiftRolesShift.fromJson(dynamic json):
@@ -214,6 +216,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']), locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']),
description = json['description'] == null ? null : nativeFromJson<String>(json['description']), description = json['description'] == null ? null : nativeFromJson<String>(json['description']),
orderId = nativeFromJson<String>(json['orderId']), orderId = nativeFromJson<String>(json['orderId']),
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
durationDays = json['durationDays'] == null ? null : nativeFromJson<int>(json['durationDays']),
order = ListShiftRolesByVendorIdShiftRolesShiftOrder.fromJson(json['order']); order = ListShiftRolesByVendorIdShiftRolesShiftOrder.fromJson(json['order']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -232,11 +236,13 @@ class ListShiftRolesByVendorIdShiftRolesShift {
locationAddress == otherTyped.locationAddress && locationAddress == otherTyped.locationAddress &&
description == otherTyped.description && description == otherTyped.description &&
orderId == otherTyped.orderId && orderId == otherTyped.orderId &&
status == otherTyped.status &&
durationDays == otherTyped.durationDays &&
order == otherTyped.order; order == otherTyped.order;
} }
@override @override
int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, location.hashCode, locationAddress.hashCode, description.hashCode, orderId.hashCode, order.hashCode]); int get hashCode => Object.hashAll([id.hashCode, title.hashCode, date.hashCode, location.hashCode, locationAddress.hashCode, description.hashCode, orderId.hashCode, status.hashCode, durationDays.hashCode, order.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -256,6 +262,14 @@ class ListShiftRolesByVendorIdShiftRolesShift {
json['description'] = nativeToJson<String?>(description); json['description'] = nativeToJson<String?>(description);
} }
json['orderId'] = nativeToJson<String>(orderId); json['orderId'] = nativeToJson<String>(orderId);
if (status != null) {
json['status'] =
shiftStatusSerializer(status!)
;
}
if (durationDays != null) {
json['durationDays'] = nativeToJson<int?>(durationDays);
}
json['order'] = order.toJson(); json['order'] = order.toJson();
return json; return json;
} }
@@ -268,6 +282,8 @@ class ListShiftRolesByVendorIdShiftRolesShift {
this.locationAddress, this.locationAddress,
this.description, this.description,
required this.orderId, required this.orderId,
this.status,
this.durationDays,
required this.order, required this.order,
}); });
} }

View File

@@ -3,10 +3,12 @@ import 'package:krow_domain/krow_domain.dart' as domain;
class StaffSession { class StaffSession {
final domain.User user; final domain.User user;
final domain.Staff? staff; final domain.Staff? staff;
final String? ownerId;
const StaffSession({ const StaffSession({
required this.user, required this.user,
this.staff, this.staff,
this.ownerId,
}); });
} }

View File

@@ -26,6 +26,9 @@ class Shift extends Equatable {
final int? durationDays; // For multi-day shifts final int? durationDays; // For multi-day shifts
final int? requiredSlots; final int? requiredSlots;
final int? filledSlots; final int? filledSlots;
final String? roleId;
final bool? hasApplied;
final double? totalValue;
const Shift({ const Shift({
required this.id, required this.id,
@@ -53,6 +56,9 @@ class Shift extends Equatable {
this.durationDays, this.durationDays,
this.requiredSlots, this.requiredSlots,
this.filledSlots, this.filledSlots,
this.roleId,
this.hasApplied,
this.totalValue,
}); });
@override @override
@@ -82,6 +88,9 @@ class Shift extends Equatable {
durationDays, durationDays,
requiredSlots, requiredSlots,
filledSlots, filledSlots,
roleId,
hasApplied,
totalValue,
]; ];
} }

View File

@@ -168,7 +168,11 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
avatar: staffRecord.photoUrl, avatar: staffRecord.photoUrl,
); );
StaffSessionStore.instance.setSession( StaffSessionStore.instance.setSession(
StaffSession(user: domainUser, staff: domainStaff), StaffSession(
user: domainUser,
staff: domainStaff,
ownerId: staffRecord?.ownerId,
),
); );
return domainUser; return domainUser;
} }

View File

@@ -60,7 +60,7 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
if (session != null) { if (session != null) {
StaffSessionStore.instance.setSession( StaffSessionStore.instance.setSession(
StaffSession(user: session.user, staff: staff), StaffSession(user: session.user, staff: staff, ownerId: session.ownerId),
); );
} }
} }

View File

@@ -156,120 +156,254 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
@override @override
Future<List<Shift>> getAvailableShifts(String query, String type) async { Future<List<Shift>> getAvailableShifts(String query, String type) async {
try { try {
final result = await _dataConnect.listShifts().execute(); final String? vendorId =
final allShifts = result.data.shifts; dc.StaffSessionStore.instance.session?.ownerId;
if (vendorId == null || vendorId.isEmpty) {
final List<Shift> mappedShifts = []; return <Shift>[];
for (final s in allShifts) {
// For each shift, map to Domain Shift
// Note: date fields in generated code might be specific types
final startDt = _toDateTime(s.startTime);
final endDt = _toDateTime(s.endTime);
final createdDt = _toDateTime(s.createdAt);
mappedShifts.add(Shift(
id: s.id,
title: s.title,
clientName: s.order.business.businessName,
logoUrl: null,
hourlyRate: s.cost ?? 0.0,
location: s.location ?? '',
locationAddress: s.locationAddress ?? '',
date: startDt?.toIso8601String() ?? '',
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
createdDate: createdDt?.toIso8601String() ?? '',
status: s.status?.stringValue.toLowerCase() ?? 'open',
description: s.description,
durationDays: s.durationDays,
requiredSlots: null, // Basic list doesn't fetch detailed role stats yet
filledSlots: null,
));
}
if (query.isNotEmpty) {
return mappedShifts.where((s) =>
s.title.toLowerCase().contains(query.toLowerCase()) ||
s.clientName.toLowerCase().contains(query.toLowerCase())
).toList();
}
return mappedShifts;
} catch (e) {
return <Shift>[];
} }
}
@override final result = await _dataConnect
Future<Shift?> getShiftDetails(String shiftId) async { .listShiftRolesByVendorId(vendorId: vendorId)
return _getShiftDetails(shiftId); .execute();
} final allShiftRoles = result.data.shiftRoles;
Future<Shift?> _getShiftDetails(String shiftId) async {
try {
final result = await _dataConnect.getShiftById(id: shiftId).execute();
final s = result.data.shift;
if (s == null) return null;
int? required;
int? filled;
try {
final rolesRes = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
if (rolesRes.data.shiftRoles.isNotEmpty) {
required = 0;
filled = 0;
for(var r in rolesRes.data.shiftRoles) {
required = (required ?? 0) + r.count;
filled = (filled ?? 0) + (r.assigned ?? 0);
}
}
} catch (_) {}
final startDt = _toDateTime(s.startTime); final List<Shift> mappedShifts = [];
final endDt = _toDateTime(s.endTime); for (final sr in allShiftRoles) {
final createdDt = _toDateTime(s.createdAt); final startDt = _toDateTime(sr.startTime);
final endDt = _toDateTime(sr.endTime);
return Shift( final createdDt = _toDateTime(sr.createdAt);
id: s.id, mappedShifts.add(
title: s.title, Shift(
clientName: s.order.business.businessName, id: sr.shiftId,
roleId: sr.roleId,
title: sr.role.name,
clientName: sr.shift.order.business.businessName,
logoUrl: null, logoUrl: null,
hourlyRate: s.cost ?? 0.0, hourlyRate: sr.role.costPerHour,
location: s.location ?? '', location: sr.shift.location ?? '',
locationAddress: s.locationAddress ?? '', locationAddress: sr.shift.locationAddress ?? '',
date: startDt?.toIso8601String() ?? '', date: startDt?.toIso8601String() ?? '',
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', startTime:
startDt != null ? DateFormat('HH:mm').format(startDt) : '',
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
createdDate: createdDt?.toIso8601String() ?? '', createdDate: createdDt?.toIso8601String() ?? '',
status: s.status?.stringValue ?? 'OPEN', status: sr.shift.status?.stringValue.toLowerCase() ?? 'open',
description: s.description, description: sr.shift.description,
durationDays: s.durationDays, durationDays: sr.shift.durationDays,
requiredSlots: required, requiredSlots: sr.count,
filledSlots: filled, filledSlots: sr.assigned ?? 0,
),
); );
} catch (e) {
return null;
} }
if (query.isNotEmpty) {
return mappedShifts
.where(
(s) =>
s.title.toLowerCase().contains(query.toLowerCase()) ||
s.clientName.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
return mappedShifts;
} catch (e) {
return <Shift>[];
}
} }
@override @override
Future<void> applyForShift(String shiftId, {bool isInstantBook = false}) async { Future<Shift?> getShiftDetails(String shiftId, {String? roleId}) async {
final rolesResult = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute(); return _getShiftDetails(shiftId, roleId: roleId);
if (rolesResult.data.shiftRoles.isEmpty) throw Exception('No open roles for this shift'); }
Future<Shift?> _getShiftDetails(String shiftId, {String? roleId}) async {
try {
if (roleId != null && roleId.isNotEmpty) {
final roleResult = await _dataConnect
.getShiftRoleById(shiftId: shiftId, roleId: roleId)
.execute();
final sr = roleResult.data.shiftRole;
if (sr == null) return null;
final DateTime? startDt = _toDateTime(sr.startTime);
final DateTime? endDt = _toDateTime(sr.endTime);
final DateTime? createdDt = _toDateTime(sr.createdAt);
final String? staffId = _auth.currentUser?.uid;
bool hasApplied = false;
String status = 'open';
if (staffId != null) {
final apps =
await _dataConnect.getApplicationsByStaffId(staffId: staffId).execute();
final app = apps.data.applications
.where(
(a) =>
a.shiftId == shiftId && a.shiftRole.roleId == roleId,
)
.firstOrNull;
if (app != null) {
hasApplied = true;
if (app.status is dc.Known<dc.ApplicationStatus>) {
final dc.ApplicationStatus s =
(app.status as dc.Known<dc.ApplicationStatus>).value;
status = _mapStatus(s);
}
}
}
return Shift(
id: sr.shiftId,
roleId: sr.roleId,
title: sr.shift.order.business.businessName,
clientName: sr.shift.order.business.businessName,
logoUrl: sr.shift.order.business.companyLogoUrl,
hourlyRate: sr.role.costPerHour,
location: sr.shift.order.teamHub.hubName,
locationAddress: '',
date: startDt?.toIso8601String() ?? '',
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
createdDate: createdDt?.toIso8601String() ?? '',
status: status,
description: sr.shift.description,
durationDays: null,
requiredSlots: sr.count,
filledSlots: sr.assigned ?? 0,
hasApplied: hasApplied,
totalValue: sr.totalValue,
);
}
final result = await _dataConnect.getShiftById(id: shiftId).execute();
final s = result.data.shift;
if (s == null) return null;
final role = rolesResult.data.shiftRoles.first; int? required;
int? filled;
final staffId = await _getStaffId(); try {
await _dataConnect.createApplication( final rolesRes = await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
shiftId: shiftId, if (rolesRes.data.shiftRoles.isNotEmpty) {
staffId: staffId, required = 0;
roleId: role.roleId, filled = 0;
status: isInstantBook ? dc.ApplicationStatus.ACCEPTED : dc.ApplicationStatus.PENDING, for(var r in rolesRes.data.shiftRoles) {
origin: dc.ApplicationOrigin.STAFF, required = (required ?? 0) + r.count;
).execute(); filled = (filled ?? 0) + (r.assigned ?? 0);
}
}
} catch (_) {}
final startDt = _toDateTime(s.startTime);
final endDt = _toDateTime(s.endTime);
final createdDt = _toDateTime(s.createdAt);
return Shift(
id: s.id,
title: s.title,
clientName: s.order.business.businessName,
logoUrl: null,
hourlyRate: s.cost ?? 0.0,
location: s.location ?? '',
locationAddress: s.locationAddress ?? '',
date: startDt?.toIso8601String() ?? '',
startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '',
endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '',
createdDate: createdDt?.toIso8601String() ?? '',
status: s.status?.stringValue ?? 'OPEN',
description: s.description,
durationDays: s.durationDays,
requiredSlots: required,
filledSlots: filled,
);
} catch (e) {
return null;
}
}
@override
Future<void> applyForShift(
String shiftId, {
bool isInstantBook = false,
String? roleId,
}) async {
final staffId = await _getStaffId();
String targetRoleId = roleId ?? '';
if (targetRoleId.isEmpty) {
final rolesResult =
await _dataConnect.listShiftRolesByShiftId(shiftId: shiftId).execute();
if (rolesResult.data.shiftRoles.isEmpty) {
throw Exception('No open roles for this shift');
}
final sr = rolesResult.data.shiftRoles.firstWhere(
(r) => (r.assigned ?? 0) < r.count,
orElse: () => rolesResult.data.shiftRoles.first,
);
targetRoleId = sr.roleId;
}
final roleResult = await _dataConnect
.getShiftRoleById(shiftId: shiftId, roleId: targetRoleId)
.execute();
final role = roleResult.data.shiftRole;
if (role == null) {
throw Exception('Shift role not found');
}
final int assigned = role.assigned ?? 0;
if (assigned >= role.count) {
throw Exception('This shift is full.');
}
final shiftResult = await _dataConnect.getShiftById(id: shiftId).execute();
final shift = shiftResult.data.shift;
if (shift == null) {
throw Exception('Shift not found');
}
final int filled = shift.filled ?? 0;
String? appId;
bool updatedRole = false;
bool updatedShift = false;
try {
final appResult = await _dataConnect
.createApplication(
shiftId: shiftId,
staffId: staffId,
roleId: targetRoleId,
status: dc.ApplicationStatus.ACCEPTED,
origin: dc.ApplicationOrigin.STAFF,
)
// TODO: this should be PENDING so a vendor can accept it.
.execute();
appId = appResult.data.application_insert.id;
await _dataConnect
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
.assigned(assigned + 1)
.execute();
updatedRole = true;
await _dataConnect
.updateShift(id: shiftId)
.filled(filled + 1)
.execute();
updatedShift = true;
} catch (e) {
if (updatedShift) {
await _dataConnect.updateShift(id: shiftId).filled(filled).execute();
}
if (updatedRole) {
await _dataConnect
.updateShiftRole(shiftId: shiftId, roleId: targetRoleId)
.assigned(assigned)
.execute();
}
if (appId != null) {
await _dataConnect.deleteApplication(id: appId).execute();
}
rethrow;
}
} }
@override @override
@@ -333,4 +467,3 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
.execute(); .execute();
} }
} }

View File

@@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';
class GetShiftDetailsArguments extends Equatable {
final String shiftId;
final String? roleId;
const GetShiftDetailsArguments({
required this.shiftId,
this.roleId,
});
@override
List<Object?> get props => [shiftId, roleId];
}

View File

@@ -15,12 +15,16 @@ abstract interface class ShiftsRepositoryInterface {
Future<List<Shift>> getPendingAssignments(); Future<List<Shift>> getPendingAssignments();
/// Retrieves detailed information for a specific shift by [shiftId]. /// Retrieves detailed information for a specific shift by [shiftId].
Future<Shift?> getShiftDetails(String shiftId); Future<Shift?> getShiftDetails(String shiftId, {String? roleId});
/// Applies for a specific open shift. /// Applies for a specific open shift.
/// ///
/// [isInstantBook] determines if the application should be immediately accepted. /// [isInstantBook] determines if the application should be immediately accepted.
Future<void> applyForShift(String shiftId, {bool isInstantBook = false}); Future<void> applyForShift(
String shiftId, {
bool isInstantBook = false,
String? roleId,
});
/// Accepts a pending shift assignment. /// Accepts a pending shift assignment.
Future<void> acceptShift(String shiftId); Future<void> acceptShift(String shiftId);

View File

@@ -5,7 +5,15 @@ class ApplyForShiftUseCase {
ApplyForShiftUseCase(this.repository); ApplyForShiftUseCase(this.repository);
Future<void> call(String shiftId, {bool isInstantBook = false}) async { Future<void> call(
return repository.applyForShift(shiftId, isInstantBook: isInstantBook); String shiftId, {
bool isInstantBook = false,
String? roleId,
}) async {
return repository.applyForShift(
shiftId,
isInstantBook: isInstantBook,
roleId: roleId,
);
} }
} }

View File

@@ -1,14 +1,18 @@
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../arguments/get_shift_details_arguments.dart';
import '../repositories/shifts_repository_interface.dart'; import '../repositories/shifts_repository_interface.dart';
class GetShiftDetailsUseCase extends UseCase<String, Shift?> { class GetShiftDetailsUseCase extends UseCase<GetShiftDetailsArguments, Shift?> {
final ShiftsRepositoryInterface repository; final ShiftsRepositoryInterface repository;
GetShiftDetailsUseCase(this.repository); GetShiftDetailsUseCase(this.repository);
@override @override
Future<Shift?> call(String params) { Future<Shift?> call(GetShiftDetailsArguments params) {
return repository.getShiftDetails(params); return repository.getShiftDetails(
params.shiftId,
roleId: params.roleId,
);
} }
} }

View File

@@ -2,6 +2,7 @@ import 'package:bloc/bloc.dart';
import '../../../domain/usecases/apply_for_shift_usecase.dart'; import '../../../domain/usecases/apply_for_shift_usecase.dart';
import '../../../domain/usecases/decline_shift_usecase.dart'; import '../../../domain/usecases/decline_shift_usecase.dart';
import '../../../domain/usecases/get_shift_details_usecase.dart'; import '../../../domain/usecases/get_shift_details_usecase.dart';
import '../../../domain/arguments/get_shift_details_arguments.dart';
import 'shift_details_event.dart'; import 'shift_details_event.dart';
import 'shift_details_state.dart'; import 'shift_details_state.dart';
@@ -26,7 +27,12 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
) async { ) async {
emit(ShiftDetailsLoading()); emit(ShiftDetailsLoading());
try { try {
final shift = await getShiftDetails(event.shiftId); final shift = await getShiftDetails(
GetShiftDetailsArguments(
shiftId: event.shiftId,
roleId: event.roleId,
),
);
if (shift != null) { if (shift != null) {
emit(ShiftDetailsLoaded(shift)); emit(ShiftDetailsLoaded(shift));
} else { } else {
@@ -42,7 +48,11 @@ class ShiftDetailsBloc extends Bloc<ShiftDetailsEvent, ShiftDetailsState> {
Emitter<ShiftDetailsState> emit, Emitter<ShiftDetailsState> emit,
) async { ) async {
try { try {
await applyForShift(event.shiftId, isInstantBook: true); await applyForShift(
event.shiftId,
isInstantBook: true,
roleId: event.roleId,
);
emit(const ShiftActionSuccess("Shift successfully booked!")); emit(const ShiftActionSuccess("Shift successfully booked!"));
} catch (e) { } catch (e) {
emit(ShiftDetailsError(e.toString())); emit(ShiftDetailsError(e.toString()));

View File

@@ -9,18 +9,20 @@ abstract class ShiftDetailsEvent extends Equatable {
class LoadShiftDetailsEvent extends ShiftDetailsEvent { class LoadShiftDetailsEvent extends ShiftDetailsEvent {
final String shiftId; final String shiftId;
const LoadShiftDetailsEvent(this.shiftId); final String? roleId;
const LoadShiftDetailsEvent(this.shiftId, {this.roleId});
@override @override
List<Object?> get props => [shiftId]; List<Object?> get props => [shiftId, roleId];
} }
class BookShiftDetailsEvent extends ShiftDetailsEvent { class BookShiftDetailsEvent extends ShiftDetailsEvent {
final String shiftId; final String shiftId;
const BookShiftDetailsEvent(this.shiftId); final String? roleId;
const BookShiftDetailsEvent(this.shiftId, {this.roleId});
@override @override
List<Object?> get props => [shiftId]; List<Object?> get props => [shiftId, roleId];
} }
class DeclineShiftDetailsEvent extends ShiftDetailsEvent { class DeclineShiftDetailsEvent extends ShiftDetailsEvent {

View File

@@ -122,7 +122,12 @@ class ShiftDetailsPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider<ShiftDetailsBloc>( return BlocProvider<ShiftDetailsBloc>(
create: (_) => create: (_) =>
Modular.get<ShiftDetailsBloc>()..add(LoadShiftDetailsEvent(shiftId)), Modular.get<ShiftDetailsBloc>()..add(
LoadShiftDetailsEvent(
shiftId,
roleId: shift?.roleId,
),
),
child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>( child: BlocListener<ShiftDetailsBloc, ShiftDetailsState>(
listener: (context, state) { listener: (context, state) {
if (state is ShiftActionSuccess) { if (state is ShiftActionSuccess) {
@@ -164,7 +169,8 @@ class ShiftDetailsPage extends StatelessWidget {
} }
final duration = _calculateDuration(displayShift); final duration = _calculateDuration(displayShift);
final estimatedTotal = (displayShift.hourlyRate) * duration; final estimatedTotal =
displayShift.totalValue ?? (displayShift.hourlyRate * duration);
final openSlots = final openSlots =
(displayShift.requiredSlots ?? 0) - (displayShift.requiredSlots ?? 0) -
(displayShift.filledSlots ?? 0); (displayShift.filledSlots ?? 0);
@@ -457,20 +463,28 @@ class ShiftDetailsPage extends StatelessWidget {
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( if ((displayShift!.hasApplied != true) &&
child: ElevatedButton( (displayShift!.requiredSlots == null ||
onPressed: () => displayShift!.filledSlots == null ||
_bookShift(context, displayShift!.id), displayShift!.filledSlots! <
style: ElevatedButton.styleFrom( displayShift!.requiredSlots!))
backgroundColor: const Color(0xFF10B981), Expanded(
foregroundColor: Colors.white, child: ElevatedButton(
padding: const EdgeInsets.symmetric( onPressed: () => _bookShift(
vertical: 16, context,
displayShift!.id,
displayShift!.roleId,
), ),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 16,
),
),
child: const Text("Book Shift"),
), ),
child: const Text("Book Shift"),
), ),
),
], ],
), ),
SizedBox( SizedBox(
@@ -489,7 +503,7 @@ class ShiftDetailsPage extends StatelessWidget {
); );
} }
void _bookShift(BuildContext context, String id) { void _bookShift(BuildContext context, String id, String? roleId) {
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
@@ -505,7 +519,7 @@ class ShiftDetailsPage extends StatelessWidget {
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
BlocProvider.of<ShiftDetailsBloc>( BlocProvider.of<ShiftDetailsBloc>(
context, context,
).add(BookShiftDetailsEvent(id)); ).add(BookShiftDetailsEvent(id, roleId: roleId));
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: const Color(0xFF10B981), foregroundColor: const Color(0xFF10B981),

View File

@@ -38,6 +38,7 @@ query getShiftRoleById(
business{ business{
id id
businessName businessName
companyLogoUrl
} }
vendor{ vendor{
@@ -45,6 +46,10 @@ query getShiftRoleById(
companyName companyName
} }
teamHub{
hubName
}
} }
} }
} }
@@ -237,6 +242,7 @@ query listShiftRolesByVendorId(
shiftRoles( shiftRoles(
where: { where: {
shift: { shift: {
status: {in: [IN_PROGRESS, CONFIRMED, ASSIGNED, OPEN, PENDING]} #IN_PROGRESS? PENDING?
order: { order: {
vendorId: { eq: $vendorId } vendorId: { eq: $vendorId }
} }
@@ -274,6 +280,8 @@ query listShiftRolesByVendorId(
locationAddress locationAddress
description description
orderId orderId
status
durationDays
order { order {
id id

File diff suppressed because it is too large Load Diff