Merge branch '216-p0-staff-01-profile-setup-wizard' of https://github.com/Oloodi/krow-workforce into 216-p0-staff-01-profile-setup-wizard

This commit is contained in:
Achintha Isuru
2026-01-27 16:25:54 -05:00
20 changed files with 18947 additions and 17667 deletions

View File

@@ -1,16 +1,16 @@
# Basic Usage # Basic Usage
```dart ```dart
ExampleConnector.instance.createTeamHudDepartment(createTeamHudDepartmentVariables).execute(); ExampleConnector.instance.createFaqData(createFaqDataVariables).execute();
ExampleConnector.instance.updateTeamHudDepartment(updateTeamHudDepartmentVariables).execute(); ExampleConnector.instance.updateFaqData(updateFaqDataVariables).execute();
ExampleConnector.instance.deleteTeamHudDepartment(deleteTeamHudDepartmentVariables).execute(); ExampleConnector.instance.deleteFaqData(deleteFaqDataVariables).execute();
ExampleConnector.instance.CreateUser(createUserVariables).execute(); ExampleConnector.instance.createStaffAvailability(createStaffAvailabilityVariables).execute();
ExampleConnector.instance.UpdateUser(updateUserVariables).execute(); ExampleConnector.instance.updateStaffAvailability(updateStaffAvailabilityVariables).execute();
ExampleConnector.instance.DeleteUser(deleteUserVariables).execute(); ExampleConnector.instance.deleteStaffAvailability(deleteStaffAvailabilityVariables).execute();
ExampleConnector.instance.createVendorBenefitPlan(createVendorBenefitPlanVariables).execute(); ExampleConnector.instance.listStaffAvailabilityStats(listStaffAvailabilityStatsVariables).execute();
ExampleConnector.instance.updateVendorBenefitPlan(updateVendorBenefitPlanVariables).execute(); ExampleConnector.instance.getStaffAvailabilityStatsByStaffId(getStaffAvailabilityStatsByStaffIdVariables).execute();
ExampleConnector.instance.deleteVendorBenefitPlan(deleteVendorBenefitPlanVariables).execute(); ExampleConnector.instance.filterStaffAvailabilityStats(filterStaffAvailabilityStatsVariables).execute();
ExampleConnector.instance.getShiftRoleById(getShiftRoleByIdVariables).execute(); ExampleConnector.instance.createTaxForm(createTaxFormVariables).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.updateStaffAvailabilityStats({ ... }) await ExampleConnector.instance.updateWorkforce({ ... })
.needWorkIndex(...) .workforceNumber(...)
.execute(); .execute();
``` ```

View File

@@ -52,6 +52,7 @@ class FilterStaffStaffs {
final String? ownerId; final String? ownerId;
final bool? isRecommended; final bool? isRecommended;
final List<String>? skills; final List<String>? skills;
final List<String>? industries;
final EnumValue<BackgroundCheckStatus>? backgroundCheckStatus; final EnumValue<BackgroundCheckStatus>? backgroundCheckStatus;
final EnumValue<EmploymentType>? employmentType; final EnumValue<EmploymentType>? employmentType;
final String? initial; final String? initial;
@@ -75,6 +76,9 @@ class FilterStaffStaffs {
skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>) skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
.map((e) => nativeFromJson<String>(e)) .map((e) => nativeFromJson<String>(e))
.toList(), .toList(),
industries = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
.map((e) => nativeFromJson<String>(e))
.toList(),
backgroundCheckStatus = json['backgroundCheckStatus'] == null ? null : backgroundCheckStatusDeserializer(json['backgroundCheckStatus']), backgroundCheckStatus = json['backgroundCheckStatus'] == null ? null : backgroundCheckStatusDeserializer(json['backgroundCheckStatus']),
employmentType = json['employmentType'] == null ? null : employmentTypeDeserializer(json['employmentType']), employmentType = json['employmentType'] == null ? null : employmentTypeDeserializer(json['employmentType']),
initial = json['initial'] == null ? null : nativeFromJson<String>(json['initial']), initial = json['initial'] == null ? null : nativeFromJson<String>(json['initial']),
@@ -104,6 +108,7 @@ class FilterStaffStaffs {
ownerId == otherTyped.ownerId && ownerId == otherTyped.ownerId &&
isRecommended == otherTyped.isRecommended && isRecommended == otherTyped.isRecommended &&
skills == otherTyped.skills && skills == otherTyped.skills &&
industries == otherTyped.industries &&
backgroundCheckStatus == otherTyped.backgroundCheckStatus && backgroundCheckStatus == otherTyped.backgroundCheckStatus &&
employmentType == otherTyped.employmentType && employmentType == otherTyped.employmentType &&
initial == otherTyped.initial && initial == otherTyped.initial &&
@@ -113,7 +118,7 @@ class FilterStaffStaffs {
} }
@override @override
int get hashCode => Object.hashAll([id.hashCode, userId.hashCode, fullName.hashCode, level.hashCode, phone.hashCode, email.hashCode, photoUrl.hashCode, averageRating.hashCode, reliabilityScore.hashCode, totalShifts.hashCode, ownerId.hashCode, isRecommended.hashCode, skills.hashCode, backgroundCheckStatus.hashCode, employmentType.hashCode, initial.hashCode, englishRequired.hashCode, city.hashCode, addres.hashCode]); int get hashCode => Object.hashAll([id.hashCode, userId.hashCode, fullName.hashCode, level.hashCode, phone.hashCode, email.hashCode, photoUrl.hashCode, averageRating.hashCode, reliabilityScore.hashCode, totalShifts.hashCode, ownerId.hashCode, isRecommended.hashCode, skills.hashCode, industries.hashCode, backgroundCheckStatus.hashCode, employmentType.hashCode, initial.hashCode, englishRequired.hashCode, city.hashCode, addres.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -151,6 +156,9 @@ class FilterStaffStaffs {
if (skills != null) { if (skills != null) {
json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList(); json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList();
} }
if (industries != null) {
json['industries'] = industries?.map((e) => nativeToJson<String>(e)).toList();
}
if (backgroundCheckStatus != null) { if (backgroundCheckStatus != null) {
json['backgroundCheckStatus'] = json['backgroundCheckStatus'] =
backgroundCheckStatusSerializer(backgroundCheckStatus!) backgroundCheckStatusSerializer(backgroundCheckStatus!)
@@ -190,6 +198,7 @@ class FilterStaffStaffs {
this.ownerId, this.ownerId,
this.isRecommended, this.isRecommended,
this.skills, this.skills,
this.industries,
this.backgroundCheckStatus, this.backgroundCheckStatus,
this.employmentType, this.employmentType,
this.initial, this.initial,

View File

@@ -0,0 +1,268 @@
part of 'generated.dart';
class GetCompletedShiftsByBusinessIdVariablesBuilder {
String businessId;
Timestamp dateFrom;
Timestamp dateTo;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
final FirebaseDataConnect _dataConnect; GetCompletedShiftsByBusinessIdVariablesBuilder offset(int? t) {
_offset.value = t;
return this;
}
GetCompletedShiftsByBusinessIdVariablesBuilder limit(int? t) {
_limit.value = t;
return this;
}
GetCompletedShiftsByBusinessIdVariablesBuilder(this._dataConnect, {required this.businessId,required this.dateFrom,required this.dateTo,});
Deserializer<GetCompletedShiftsByBusinessIdData> dataDeserializer = (dynamic json) => GetCompletedShiftsByBusinessIdData.fromJson(jsonDecode(json));
Serializer<GetCompletedShiftsByBusinessIdVariables> varsSerializer = (GetCompletedShiftsByBusinessIdVariables vars) => jsonEncode(vars.toJson());
Future<QueryResult<GetCompletedShiftsByBusinessIdData, GetCompletedShiftsByBusinessIdVariables>> execute() {
return ref().execute();
}
QueryRef<GetCompletedShiftsByBusinessIdData, GetCompletedShiftsByBusinessIdVariables> ref() {
GetCompletedShiftsByBusinessIdVariables vars= GetCompletedShiftsByBusinessIdVariables(businessId: businessId,dateFrom: dateFrom,dateTo: dateTo,offset: _offset,limit: _limit,);
return _dataConnect.query("getCompletedShiftsByBusinessId", dataDeserializer, varsSerializer, vars);
}
}
@immutable
class GetCompletedShiftsByBusinessIdShifts {
final String id;
final Timestamp? date;
final Timestamp? startTime;
final Timestamp? endTime;
final double? hours;
final double? cost;
final int? workersNeeded;
final int? filled;
final Timestamp? createdAt;
final GetCompletedShiftsByBusinessIdShiftsOrder order;
GetCompletedShiftsByBusinessIdShifts.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
date = json['date'] == null ? null : Timestamp.fromJson(json['date']),
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
cost = json['cost'] == null ? null : nativeFromJson<double>(json['cost']),
workersNeeded = json['workersNeeded'] == null ? null : nativeFromJson<int>(json['workersNeeded']),
filled = json['filled'] == null ? null : nativeFromJson<int>(json['filled']),
createdAt = json['createdAt'] == null ? null : Timestamp.fromJson(json['createdAt']),
order = GetCompletedShiftsByBusinessIdShiftsOrder.fromJson(json['order']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final GetCompletedShiftsByBusinessIdShifts otherTyped = other as GetCompletedShiftsByBusinessIdShifts;
return id == otherTyped.id &&
date == otherTyped.date &&
startTime == otherTyped.startTime &&
endTime == otherTyped.endTime &&
hours == otherTyped.hours &&
cost == otherTyped.cost &&
workersNeeded == otherTyped.workersNeeded &&
filled == otherTyped.filled &&
createdAt == otherTyped.createdAt &&
order == otherTyped.order;
}
@override
int get hashCode => Object.hashAll([id.hashCode, date.hashCode, startTime.hashCode, endTime.hashCode, hours.hashCode, cost.hashCode, workersNeeded.hashCode, filled.hashCode, createdAt.hashCode, order.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
if (date != null) {
json['date'] = date!.toJson();
}
if (startTime != null) {
json['startTime'] = startTime!.toJson();
}
if (endTime != null) {
json['endTime'] = endTime!.toJson();
}
if (hours != null) {
json['hours'] = nativeToJson<double?>(hours);
}
if (cost != null) {
json['cost'] = nativeToJson<double?>(cost);
}
if (workersNeeded != null) {
json['workersNeeded'] = nativeToJson<int?>(workersNeeded);
}
if (filled != null) {
json['filled'] = nativeToJson<int?>(filled);
}
if (createdAt != null) {
json['createdAt'] = createdAt!.toJson();
}
json['order'] = order.toJson();
return json;
}
GetCompletedShiftsByBusinessIdShifts({
required this.id,
this.date,
this.startTime,
this.endTime,
this.hours,
this.cost,
this.workersNeeded,
this.filled,
this.createdAt,
required this.order,
});
}
@immutable
class GetCompletedShiftsByBusinessIdShiftsOrder {
final EnumValue<OrderStatus> status;
GetCompletedShiftsByBusinessIdShiftsOrder.fromJson(dynamic json):
status = orderStatusDeserializer(json['status']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final GetCompletedShiftsByBusinessIdShiftsOrder otherTyped = other as GetCompletedShiftsByBusinessIdShiftsOrder;
return status == otherTyped.status;
}
@override
int get hashCode => status.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['status'] =
orderStatusSerializer(status)
;
return json;
}
GetCompletedShiftsByBusinessIdShiftsOrder({
required this.status,
});
}
@immutable
class GetCompletedShiftsByBusinessIdData {
final List<GetCompletedShiftsByBusinessIdShifts> shifts;
GetCompletedShiftsByBusinessIdData.fromJson(dynamic json):
shifts = (json['shifts'] as List<dynamic>)
.map((e) => GetCompletedShiftsByBusinessIdShifts.fromJson(e))
.toList();
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final GetCompletedShiftsByBusinessIdData otherTyped = other as GetCompletedShiftsByBusinessIdData;
return shifts == otherTyped.shifts;
}
@override
int get hashCode => shifts.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['shifts'] = shifts.map((e) => e.toJson()).toList();
return json;
}
GetCompletedShiftsByBusinessIdData({
required this.shifts,
});
}
@immutable
class GetCompletedShiftsByBusinessIdVariables {
final String businessId;
final Timestamp dateFrom;
final Timestamp dateTo;
late final Optional<int>offset;
late final Optional<int>limit;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
GetCompletedShiftsByBusinessIdVariables.fromJson(Map<String, dynamic> json):
businessId = nativeFromJson<String>(json['businessId']),
dateFrom = Timestamp.fromJson(json['dateFrom']),
dateTo = Timestamp.fromJson(json['dateTo']) {
offset = Optional.optional(nativeFromJson, nativeToJson);
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
}
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final GetCompletedShiftsByBusinessIdVariables otherTyped = other as GetCompletedShiftsByBusinessIdVariables;
return businessId == otherTyped.businessId &&
dateFrom == otherTyped.dateFrom &&
dateTo == otherTyped.dateTo &&
offset == otherTyped.offset &&
limit == otherTyped.limit;
}
@override
int get hashCode => Object.hashAll([businessId.hashCode, dateFrom.hashCode, dateTo.hashCode, offset.hashCode, limit.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['businessId'] = nativeToJson<String>(businessId);
json['dateFrom'] = dateFrom.toJson();
json['dateTo'] = dateTo.toJson();
if(offset.state == OptionalState.set) {
json['offset'] = offset.toJson();
}
if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson();
}
return json;
}
GetCompletedShiftsByBusinessIdVariables({
required this.businessId,
required this.dateFrom,
required this.dateTo,
required this.offset,
required this.limit,
});
}

View File

@@ -6,6 +6,7 @@ class ListShiftRolesByBusinessAndDateRangeVariablesBuilder {
Timestamp end; Timestamp end;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson); Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson); Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
Optional<ShiftStatus> _status = Optional.optional((data) => ShiftStatus.values.byName(data), enumSerializer);
final FirebaseDataConnect _dataConnect; ListShiftRolesByBusinessAndDateRangeVariablesBuilder offset(int? t) { final FirebaseDataConnect _dataConnect; ListShiftRolesByBusinessAndDateRangeVariablesBuilder offset(int? t) {
_offset.value = t; _offset.value = t;
@@ -15,6 +16,10 @@ class ListShiftRolesByBusinessAndDateRangeVariablesBuilder {
_limit.value = t; _limit.value = t;
return this; return this;
} }
ListShiftRolesByBusinessAndDateRangeVariablesBuilder status(ShiftStatus? t) {
_status.value = t;
return this;
}
ListShiftRolesByBusinessAndDateRangeVariablesBuilder(this._dataConnect, {required this.businessId,required this.start,required this.end,}); ListShiftRolesByBusinessAndDateRangeVariablesBuilder(this._dataConnect, {required this.businessId,required this.start,required this.end,});
Deserializer<ListShiftRolesByBusinessAndDateRangeData> dataDeserializer = (dynamic json) => ListShiftRolesByBusinessAndDateRangeData.fromJson(jsonDecode(json)); Deserializer<ListShiftRolesByBusinessAndDateRangeData> dataDeserializer = (dynamic json) => ListShiftRolesByBusinessAndDateRangeData.fromJson(jsonDecode(json));
@@ -24,7 +29,7 @@ class ListShiftRolesByBusinessAndDateRangeVariablesBuilder {
} }
QueryRef<ListShiftRolesByBusinessAndDateRangeData, ListShiftRolesByBusinessAndDateRangeVariables> ref() { QueryRef<ListShiftRolesByBusinessAndDateRangeData, ListShiftRolesByBusinessAndDateRangeVariables> ref() {
ListShiftRolesByBusinessAndDateRangeVariables vars= ListShiftRolesByBusinessAndDateRangeVariables(businessId: businessId,start: start,end: end,offset: _offset,limit: _limit,); ListShiftRolesByBusinessAndDateRangeVariables vars= ListShiftRolesByBusinessAndDateRangeVariables(businessId: businessId,start: start,end: end,offset: _offset,limit: _limit,status: _status,);
return _dataConnect.query("listShiftRolesByBusinessAndDateRange", dataDeserializer, varsSerializer, vars); return _dataConnect.query("listShiftRolesByBusinessAndDateRange", dataDeserializer, varsSerializer, vars);
} }
} }
@@ -308,6 +313,7 @@ class ListShiftRolesByBusinessAndDateRangeVariables {
final Timestamp end; final Timestamp end;
late final Optional<int>offset; late final Optional<int>offset;
late final Optional<int>limit; late final Optional<int>limit;
late final Optional<ShiftStatus>status;
@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.')
ListShiftRolesByBusinessAndDateRangeVariables.fromJson(Map<String, dynamic> json): ListShiftRolesByBusinessAndDateRangeVariables.fromJson(Map<String, dynamic> json):
@@ -326,6 +332,10 @@ class ListShiftRolesByBusinessAndDateRangeVariables {
limit = Optional.optional(nativeFromJson, nativeToJson); limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']); limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
status = Optional.optional((data) => ShiftStatus.values.byName(data), enumSerializer);
status.value = json['status'] == null ? null : ShiftStatus.values.byName(json['status']);
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -341,11 +351,12 @@ class ListShiftRolesByBusinessAndDateRangeVariables {
start == otherTyped.start && start == otherTyped.start &&
end == otherTyped.end && end == otherTyped.end &&
offset == otherTyped.offset && offset == otherTyped.offset &&
limit == otherTyped.limit; limit == otherTyped.limit &&
status == otherTyped.status;
} }
@override @override
int get hashCode => Object.hashAll([businessId.hashCode, start.hashCode, end.hashCode, offset.hashCode, limit.hashCode]); int get hashCode => Object.hashAll([businessId.hashCode, start.hashCode, end.hashCode, offset.hashCode, limit.hashCode, status.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -359,6 +370,9 @@ class ListShiftRolesByBusinessAndDateRangeVariables {
if(limit.state == OptionalState.set) { if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson(); json['limit'] = limit.toJson();
} }
if(status.state == OptionalState.set) {
json['status'] = status.toJson();
}
return json; return json;
} }
@@ -368,6 +382,7 @@ class ListShiftRolesByBusinessAndDateRangeVariables {
required this.end, required this.end,
required this.offset, required this.offset,
required this.limit, required this.limit,
required this.status,
}); });
} }

View File

@@ -0,0 +1,407 @@
part of 'generated.dart';
class ListStaffsApplicationsByBusinessForDayVariablesBuilder {
String businessId;
Timestamp dayStart;
Timestamp dayEnd;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
final FirebaseDataConnect _dataConnect; ListStaffsApplicationsByBusinessForDayVariablesBuilder offset(int? t) {
_offset.value = t;
return this;
}
ListStaffsApplicationsByBusinessForDayVariablesBuilder limit(int? t) {
_limit.value = t;
return this;
}
ListStaffsApplicationsByBusinessForDayVariablesBuilder(this._dataConnect, {required this.businessId,required this.dayStart,required this.dayEnd,});
Deserializer<ListStaffsApplicationsByBusinessForDayData> dataDeserializer = (dynamic json) => ListStaffsApplicationsByBusinessForDayData.fromJson(jsonDecode(json));
Serializer<ListStaffsApplicationsByBusinessForDayVariables> varsSerializer = (ListStaffsApplicationsByBusinessForDayVariables vars) => jsonEncode(vars.toJson());
Future<QueryResult<ListStaffsApplicationsByBusinessForDayData, ListStaffsApplicationsByBusinessForDayVariables>> execute() {
return ref().execute();
}
QueryRef<ListStaffsApplicationsByBusinessForDayData, ListStaffsApplicationsByBusinessForDayVariables> ref() {
ListStaffsApplicationsByBusinessForDayVariables vars= ListStaffsApplicationsByBusinessForDayVariables(businessId: businessId,dayStart: dayStart,dayEnd: dayEnd,offset: _offset,limit: _limit,);
return _dataConnect.query("listStaffsApplicationsByBusinessForDay", dataDeserializer, varsSerializer, vars);
}
}
@immutable
class ListStaffsApplicationsByBusinessForDayApplications {
final String id;
final String shiftId;
final String roleId;
final Timestamp? checkInTime;
final Timestamp? checkOutTime;
final Timestamp? appliedAt;
final EnumValue<ApplicationStatus> status;
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRole shiftRole;
final ListStaffsApplicationsByBusinessForDayApplicationsStaff staff;
ListStaffsApplicationsByBusinessForDayApplications.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
shiftId = nativeFromJson<String>(json['shiftId']),
roleId = nativeFromJson<String>(json['roleId']),
checkInTime = json['checkInTime'] == null ? null : Timestamp.fromJson(json['checkInTime']),
checkOutTime = json['checkOutTime'] == null ? null : Timestamp.fromJson(json['checkOutTime']),
appliedAt = json['appliedAt'] == null ? null : Timestamp.fromJson(json['appliedAt']),
status = applicationStatusDeserializer(json['status']),
shiftRole = ListStaffsApplicationsByBusinessForDayApplicationsShiftRole.fromJson(json['shiftRole']),
staff = ListStaffsApplicationsByBusinessForDayApplicationsStaff.fromJson(json['staff']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayApplications otherTyped = other as ListStaffsApplicationsByBusinessForDayApplications;
return id == otherTyped.id &&
shiftId == otherTyped.shiftId &&
roleId == otherTyped.roleId &&
checkInTime == otherTyped.checkInTime &&
checkOutTime == otherTyped.checkOutTime &&
appliedAt == otherTyped.appliedAt &&
status == otherTyped.status &&
shiftRole == otherTyped.shiftRole &&
staff == otherTyped.staff;
}
@override
int get hashCode => Object.hashAll([id.hashCode, shiftId.hashCode, roleId.hashCode, checkInTime.hashCode, checkOutTime.hashCode, appliedAt.hashCode, status.hashCode, shiftRole.hashCode, staff.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
json['shiftId'] = nativeToJson<String>(shiftId);
json['roleId'] = nativeToJson<String>(roleId);
if (checkInTime != null) {
json['checkInTime'] = checkInTime!.toJson();
}
if (checkOutTime != null) {
json['checkOutTime'] = checkOutTime!.toJson();
}
if (appliedAt != null) {
json['appliedAt'] = appliedAt!.toJson();
}
json['status'] =
applicationStatusSerializer(status)
;
json['shiftRole'] = shiftRole.toJson();
json['staff'] = staff.toJson();
return json;
}
ListStaffsApplicationsByBusinessForDayApplications({
required this.id,
required this.shiftId,
required this.roleId,
this.checkInTime,
this.checkOutTime,
this.appliedAt,
required this.status,
required this.shiftRole,
required this.staff,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayApplicationsShiftRole {
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift shift;
final int count;
final int? assigned;
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole role;
ListStaffsApplicationsByBusinessForDayApplicationsShiftRole.fromJson(dynamic json):
shift = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(json['shift']),
count = nativeFromJson<int>(json['count']),
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
role = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole.fromJson(json['role']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRole otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRole;
return shift == otherTyped.shift &&
count == otherTyped.count &&
assigned == otherTyped.assigned &&
role == otherTyped.role;
}
@override
int get hashCode => Object.hashAll([shift.hashCode, count.hashCode, assigned.hashCode, role.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['shift'] = shift.toJson();
json['count'] = nativeToJson<int>(count);
if (assigned != null) {
json['assigned'] = nativeToJson<int?>(assigned);
}
json['role'] = role.toJson();
return json;
}
ListStaffsApplicationsByBusinessForDayApplicationsShiftRole({
required this.shift,
required this.count,
this.assigned,
required this.role,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift {
final String? location;
final double? cost;
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(dynamic json):
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
cost = json['cost'] == null ? null : nativeFromJson<double>(json['cost']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift;
return location == otherTyped.location &&
cost == otherTyped.cost;
}
@override
int get hashCode => Object.hashAll([location.hashCode, cost.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
if (location != null) {
json['location'] = nativeToJson<String?>(location);
}
if (cost != null) {
json['cost'] = nativeToJson<double?>(cost);
}
return json;
}
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift({
this.location,
this.cost,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole {
final String name;
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole.fromJson(dynamic json):
name = nativeFromJson<String>(json['name']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole;
return name == otherTyped.name;
}
@override
int get hashCode => name.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['name'] = nativeToJson<String>(name);
return json;
}
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole({
required this.name,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayApplicationsStaff {
final String id;
final String fullName;
final String? email;
final String? phone;
final String? photoUrl;
ListStaffsApplicationsByBusinessForDayApplicationsStaff.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
fullName = nativeFromJson<String>(json['fullName']),
email = json['email'] == null ? null : nativeFromJson<String>(json['email']),
phone = json['phone'] == null ? null : nativeFromJson<String>(json['phone']),
photoUrl = json['photoUrl'] == null ? null : nativeFromJson<String>(json['photoUrl']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayApplicationsStaff otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsStaff;
return id == otherTyped.id &&
fullName == otherTyped.fullName &&
email == otherTyped.email &&
phone == otherTyped.phone &&
photoUrl == otherTyped.photoUrl;
}
@override
int get hashCode => Object.hashAll([id.hashCode, fullName.hashCode, email.hashCode, phone.hashCode, photoUrl.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
json['fullName'] = nativeToJson<String>(fullName);
if (email != null) {
json['email'] = nativeToJson<String?>(email);
}
if (phone != null) {
json['phone'] = nativeToJson<String?>(phone);
}
if (photoUrl != null) {
json['photoUrl'] = nativeToJson<String?>(photoUrl);
}
return json;
}
ListStaffsApplicationsByBusinessForDayApplicationsStaff({
required this.id,
required this.fullName,
this.email,
this.phone,
this.photoUrl,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayData {
final List<ListStaffsApplicationsByBusinessForDayApplications> applications;
ListStaffsApplicationsByBusinessForDayData.fromJson(dynamic json):
applications = (json['applications'] as List<dynamic>)
.map((e) => ListStaffsApplicationsByBusinessForDayApplications.fromJson(e))
.toList();
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayData otherTyped = other as ListStaffsApplicationsByBusinessForDayData;
return applications == otherTyped.applications;
}
@override
int get hashCode => applications.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['applications'] = applications.map((e) => e.toJson()).toList();
return json;
}
ListStaffsApplicationsByBusinessForDayData({
required this.applications,
});
}
@immutable
class ListStaffsApplicationsByBusinessForDayVariables {
final String businessId;
final Timestamp dayStart;
final Timestamp dayEnd;
late final Optional<int>offset;
late final Optional<int>limit;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
ListStaffsApplicationsByBusinessForDayVariables.fromJson(Map<String, dynamic> json):
businessId = nativeFromJson<String>(json['businessId']),
dayStart = Timestamp.fromJson(json['dayStart']),
dayEnd = Timestamp.fromJson(json['dayEnd']) {
offset = Optional.optional(nativeFromJson, nativeToJson);
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
}
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListStaffsApplicationsByBusinessForDayVariables otherTyped = other as ListStaffsApplicationsByBusinessForDayVariables;
return businessId == otherTyped.businessId &&
dayStart == otherTyped.dayStart &&
dayEnd == otherTyped.dayEnd &&
offset == otherTyped.offset &&
limit == otherTyped.limit;
}
@override
int get hashCode => Object.hashAll([businessId.hashCode, dayStart.hashCode, dayEnd.hashCode, offset.hashCode, limit.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['businessId'] = nativeToJson<String>(businessId);
json['dayStart'] = dayStart.toJson();
json['dayEnd'] = dayEnd.toJson();
if(offset.state == OptionalState.set) {
json['offset'] = offset.toJson();
}
if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson();
}
return json;
}
ListStaffsApplicationsByBusinessForDayVariables({
required this.businessId,
required this.dayStart,
required this.dayEnd,
required this.offset,
required this.limit,
});
}

View File

@@ -22,22 +22,7 @@ class InvoiceHistorySection extends StatelessWidget {
t.client_billing.invoice_history, t.client_billing.invoice_history,
style: UiTypography.title2b.textPrimary, style: UiTypography.title2b.textPrimary,
), ),
GestureDetector( const SizedBox.shrink(),
onTap: () {},
child: Row(
children: <Widget>[
Text(
t.client_billing.view_all,
style: UiTypography.footnote2b.textPrimary,
),
const Icon(
UiIcons.chevronRight,
size: 16,
color: UiColors.primary,
),
],
),
),
], ],
), ),
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
@@ -117,8 +102,7 @@ class _InvoiceItem extends StatelessWidget {
_StatusBadge(status: invoice.status), _StatusBadge(status: invoice.status),
], ],
), ),
const SizedBox(width: UiConstants.space2), const SizedBox.shrink(),
const Icon(UiIcons.download, size: 16, color: UiColors.iconSecondary),
], ],
), ),
); );

View File

@@ -69,29 +69,13 @@ class _PaymentMethodCardState extends State<PaymentMethodCard> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
t.client_billing.payment_method, t.client_billing.payment_method,
style: UiTypography.title2b.textPrimary, style: UiTypography.title2b.textPrimary,
),
GestureDetector(
onTap: () {},
child: Row(
children: <Widget>[
const Icon(
UiIcons.add,
size: 14,
color: UiColors.primary,
),
const SizedBox(width: 4),
Text(
t.client_billing.add_payment,
style: UiTypography.footnote2b.textPrimary,
),
],
),
),
],
), ),
const SizedBox.shrink(),
],
),
if (account != null) ...<Widget>[ if (account != null) ...<Widget>[
const SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space3),
Container( Container(

View File

@@ -50,25 +50,7 @@ class SavingsCard extends StatelessWidget {
style: UiTypography.footnote2r.textSecondary, style: UiTypography.footnote2r.textSecondary,
), ),
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
SizedBox( const SizedBox.shrink(),
height: 28,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
foregroundColor: UiColors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3,
),
shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusMd,
),
textStyle: UiTypography.footnote2b,
),
child: Text(t.client_billing.view_details),
),
),
], ],
), ),
), ),

View File

@@ -1,4 +1,5 @@
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'data/repositories_impl/coverage_repository_impl.dart'; import 'data/repositories_impl/coverage_repository_impl.dart';
import 'domain/repositories/coverage_repository.dart'; import 'domain/repositories/coverage_repository.dart';
import 'domain/usecases/get_coverage_stats_usecase.dart'; import 'domain/usecases/get_coverage_stats_usecase.dart';
@@ -11,7 +12,9 @@ class CoverageModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addSingleton<CoverageRepository>(CoverageRepositoryImpl.new); i.addSingleton<CoverageRepository>(
() => CoverageRepositoryImpl(dataConnect: ExampleConnector.instance),
);
// Use Cases // Use Cases
i.addSingleton(GetShiftsForDateUseCase.new); i.addSingleton(GetShiftsForDateUseCase.new);

View File

@@ -1,3 +1,5 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import '../../domain/repositories/coverage_repository.dart'; import '../../domain/repositories/coverage_repository.dart';
import '../../domain/ui_entities/coverage_entities.dart'; import '../../domain/ui_entities/coverage_entities.dart';
@@ -13,82 +15,58 @@ import '../../domain/ui_entities/coverage_entities.dart';
/// - Returns domain entities from `domain/ui_entities`. /// - Returns domain entities from `domain/ui_entities`.
class CoverageRepositoryImpl implements CoverageRepository { class CoverageRepositoryImpl implements CoverageRepository {
/// Creates a [CoverageRepositoryImpl]. /// Creates a [CoverageRepositoryImpl].
CoverageRepositoryImpl(); CoverageRepositoryImpl({required dc.ExampleConnector dataConnect})
: _dataConnect = dataConnect;
final dc.ExampleConnector _dataConnect;
/// Fetches shifts for a specific date. /// Fetches shifts for a specific date.
@override @override
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async { Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
// Simulate network delay final String? businessId =
await Future<void>.delayed(const Duration(milliseconds: 500)); dc.ClientSessionStore.instance.session?.business?.id;
print('Coverage: now=${DateTime.now().toIso8601String()}');
// Mock data - in production, this would come from data_connect if (businessId == null || businessId.isEmpty) {
final DateTime today = DateTime.now(); print('Coverage: missing businessId for date=${date.toIso8601String()}');
final bool isToday = date.year == today.year &&
date.month == today.month &&
date.day == today.day;
if (!isToday) {
// Return empty list for non-today dates
return <CoverageShift>[]; return <CoverageShift>[];
} }
return <CoverageShift>[ final DateTime start = DateTime(date.year, date.month, date.day);
CoverageShift( final DateTime end =
id: '1', DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
title: 'Banquet Server', print(
location: 'Grand Ballroom', 'Coverage: request businessId=$businessId dayStart=${start.toIso8601String()} dayEnd=${end.toIso8601String()}',
startTime: '16:00', );
workersNeeded: 10, final fdc.QueryResult<
date: date, dc.ListShiftRolesByBusinessAndDateRangeData,
workers: const <CoverageWorker>[ dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
CoverageWorker( await _dataConnect
name: 'Sarah Wilson', .listShiftRolesByBusinessAndDateRange(
status: 'confirmed', businessId: businessId,
checkInTime: '15:55', start: _toTimestamp(start),
), end: _toTimestamp(end),
CoverageWorker( )
name: 'Mike Ross', .execute();
status: 'confirmed',
checkInTime: '16:00', final fdc.QueryResult<
), dc.ListStaffsApplicationsByBusinessForDayData,
CoverageWorker( dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult =
name: 'Jane Doe', await _dataConnect
status: 'confirmed', .listStaffsApplicationsByBusinessForDay(
checkInTime: null, businessId: businessId,
), dayStart: _toTimestamp(start),
CoverageWorker( dayEnd: _toTimestamp(end),
name: 'John Smith', )
status: 'late', .execute();
checkInTime: null, print(
), 'Coverage: ${date.toIso8601String()} staffsApplications=${applicationsResult.data.applications.length}',
], );
),
CoverageShift( return _mapCoverageShifts(
id: '2', shiftRolesResult.data.shiftRoles,
title: 'Bartender', applicationsResult.data.applications,
location: 'Lobby Bar', date,
startTime: '17:00', );
workersNeeded: 4,
date: date,
workers: const <CoverageWorker>[
CoverageWorker(
name: 'Emily Blunt',
status: 'confirmed',
checkInTime: '16:45',
),
CoverageWorker(
name: 'Chris Evans',
status: 'confirmed',
checkInTime: '16:50',
),
CoverageWorker(
name: 'Tom Holland',
status: 'confirmed',
checkInTime: null,
),
],
),
];
} }
/// Fetches coverage statistics for a specific date. /// Fetches coverage statistics for a specific date.
@@ -120,4 +98,128 @@ class CoverageRepositoryImpl implements CoverageRepository {
late: late, late: late,
); );
} }
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
List<CoverageShift> _mapCoverageShifts(
List<dc.ListShiftRolesByBusinessAndDateRangeShiftRoles> shiftRoles,
List<dc.ListStaffsApplicationsByBusinessForDayApplications> applications,
DateTime date,
) {
if (shiftRoles.isEmpty && applications.isEmpty) {
return <CoverageShift>[];
}
final Map<String, _CoverageGroup> groups = <String, _CoverageGroup>{};
for (final dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole
in shiftRoles) {
final String key = '${shiftRole.shiftId}:${shiftRole.roleId}';
groups[key] = _CoverageGroup(
shiftId: shiftRole.shiftId,
roleId: shiftRole.roleId,
title: shiftRole.role.name,
location: shiftRole.shift.location ?? '',
startTime: _formatTime(shiftRole.startTime) ?? '00:00',
workersNeeded: shiftRole.count,
date: shiftRole.shift.date?.toDateTime() ?? date,
workers: <CoverageWorker>[],
);
}
for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
in applications) {
final String key = '${app.shiftId}:${app.roleId}';
final _CoverageGroup existing = groups[key] ??
_CoverageGroup(
shiftId: app.shiftId,
roleId: app.roleId,
title: app.shiftRole.role.name,
location: app.shiftRole.shift.location ?? '',
startTime: '00:00',
workersNeeded: 0,
date: date,
workers: <CoverageWorker>[],
);
existing.workers.add(
CoverageWorker(
name: app.staff.fullName,
status: _mapWorkerStatus(app.status),
checkInTime: _formatTime(app.checkInTime),
),
);
groups[key] = existing;
}
return groups.values
.map(
(_CoverageGroup group) => CoverageShift(
id: '${group.shiftId}:${group.roleId}',
title: group.title,
location: group.location,
startTime: group.startTime,
workersNeeded: group.workersNeeded,
date: group.date,
workers: group.workers,
),
)
.toList();
}
String _mapWorkerStatus(
dc.EnumValue<dc.ApplicationStatus> status,
) {
if (status is dc.Known<dc.ApplicationStatus>) {
switch (status.value) {
case dc.ApplicationStatus.LATE:
return 'late';
case dc.ApplicationStatus.CHECKED_IN:
case dc.ApplicationStatus.CHECKED_OUT:
case dc.ApplicationStatus.ACCEPTED:
case dc.ApplicationStatus.CONFIRMED:
case dc.ApplicationStatus.PENDING:
case dc.ApplicationStatus.REJECTED:
case dc.ApplicationStatus.NO_SHOW:
return 'confirmed';
}
}
return 'confirmed';
}
String? _formatTime(fdc.Timestamp? timestamp) {
if (timestamp == null) {
return null;
}
final DateTime date = timestamp.toDateTime();
final String hour = date.hour.toString().padLeft(2, '0');
final String minute = date.minute.toString().padLeft(2, '0');
return '$hour:$minute';
}
}
class _CoverageGroup {
_CoverageGroup({
required this.shiftId,
required this.roleId,
required this.title,
required this.location,
required this.startTime,
required this.workersNeeded,
required this.date,
required this.workers,
});
final String shiftId;
final String roleId;
final String title;
final String location;
final String startTime;
final int workersNeeded;
final DateTime date;
final List<CoverageWorker> workers;
} }

View File

@@ -53,7 +53,7 @@ class CoverageHeader extends StatelessWidget {
gradient: LinearGradient( gradient: LinearGradient(
colors: <Color>[ colors: <Color>[
UiColors.primary, UiColors.primary,
UiColors.accent, UiColors.primary,
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,

View File

@@ -17,8 +17,95 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
HomeRepositoryImpl(this._mock, this._dataConnect); HomeRepositoryImpl(this._mock, this._dataConnect);
@override @override
Future<HomeDashboardData> getDashboardData() { Future<HomeDashboardData> getDashboardData() async {
return _mock.getDashboardData(); final String? businessId = ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return const HomeDashboardData(
weeklySpending: 0,
next7DaysSpending: 0,
weeklyShifts: 0,
next7DaysScheduled: 0,
totalNeeded: 0,
totalFilled: 0,
);
}
final DateTime now = DateTime.now();
final int daysFromMonday = now.weekday - DateTime.monday;
final DateTime monday =
DateTime(now.year, now.month, now.day).subtract(Duration(days: daysFromMonday));
final DateTime weekRangeStart = DateTime(monday.year, monday.month, monday.day);
final DateTime weekRangeEnd =
DateTime(monday.year, monday.month, monday.day + 13, 23, 59, 59, 999);
final fdc.QueryResult<
GetCompletedShiftsByBusinessIdData,
GetCompletedShiftsByBusinessIdVariables> completedResult =
await _dataConnect
.getCompletedShiftsByBusinessId(
businessId: businessId,
dateFrom: _toTimestamp(weekRangeStart),
dateTo: _toTimestamp(weekRangeEnd),
)
.execute();
print(
'Home spending: businessId=$businessId dateFrom=${weekRangeStart.toIso8601String()} '
'dateTo=${weekRangeEnd.toIso8601String()} shifts=${completedResult.data.shifts.length}',
);
double weeklySpending = 0.0;
double next7DaysSpending = 0.0;
int weeklyShifts = 0;
int next7DaysScheduled = 0;
for (final GetCompletedShiftsByBusinessIdShifts shift
in completedResult.data.shifts) {
final DateTime? shiftDate = shift.date?.toDateTime();
if (shiftDate == null) {
continue;
}
final int offset = shiftDate.difference(weekRangeStart).inDays;
if (offset < 0 || offset > 13) {
continue;
}
final double cost = shift.cost ?? 0.0;
if (offset <= 6) {
weeklySpending += cost;
weeklyShifts += 1;
} else {
next7DaysSpending += cost;
next7DaysScheduled += 1;
}
}
final DateTime start = DateTime(now.year, now.month, now.day);
final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999);
final fdc.QueryResult<
ListShiftRolesByBusinessAndDateRangeData,
ListShiftRolesByBusinessAndDateRangeVariables> result =
await _dataConnect
.listShiftRolesByBusinessAndDateRange(
businessId: businessId,
start: _toTimestamp(start),
end: _toTimestamp(end),
)
.execute();
int totalNeeded = 0;
int totalFilled = 0;
for (final ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole
in result.data.shiftRoles) {
totalNeeded += shiftRole.count;
totalFilled += shiftRole.assigned ?? 0;
}
return HomeDashboardData(
weeklySpending: weeklySpending,
next7DaysSpending: next7DaysSpending,
weeklyShifts: weeklyShifts,
next7DaysScheduled: next7DaysScheduled,
totalNeeded: totalNeeded,
totalFilled: totalFilled,
);
} }
@override @override

View File

@@ -21,10 +21,16 @@ class ClientHomeState extends Equatable {
this.widgetOrder = const <String>[ this.widgetOrder = const <String>[
'actions', 'actions',
'reorder', 'reorder',
'coverage',
'spending',
'liveActivity',
], ],
this.widgetVisibility = const <String, bool>{ this.widgetVisibility = const <String, bool>{
'actions': true, 'actions': true,
'reorder': true, 'reorder': true,
'coverage': true,
'spending': true,
'liveActivity': true,
}, },
this.isEditMode = false, this.isEditMode = false,
this.errorMessage, this.errorMessage,

View File

@@ -94,7 +94,9 @@ class DashboardWidgetBuilder extends StatelessWidget {
: 0, : 0,
); );
case 'liveActivity': case 'liveActivity':
return LiveActivityWidget(onViewAllPressed: () {}); return LiveActivityWidget(
onViewAllPressed: () => Modular.to.navigate('/client-main/coverage/'),
);
default: default:
return const SizedBox.shrink(); return const SizedBox.shrink();
} }

View File

@@ -1,44 +1,107 @@
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'coverage_dashboard.dart'; import 'coverage_dashboard.dart';
/// A widget that displays live activity information. /// A widget that displays live activity information.
class LiveActivityWidget extends StatelessWidget { class LiveActivityWidget extends StatefulWidget {
/// Callback when "View all" is pressed. /// Callback when "View all" is pressed.
final VoidCallback onViewAllPressed; final VoidCallback onViewAllPressed;
/// Creates a [LiveActivityWidget]. /// Creates a [LiveActivityWidget].
const LiveActivityWidget({super.key, required this.onViewAllPressed}); const LiveActivityWidget({super.key, required this.onViewAllPressed});
@override
State<LiveActivityWidget> createState() => _LiveActivityWidgetState();
}
class _LiveActivityWidgetState extends State<LiveActivityWidget> {
late final Future<_LiveActivityData> _liveActivityFuture =
_loadLiveActivity();
Future<_LiveActivityData> _loadLiveActivity() async {
final String? businessId =
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return _LiveActivityData.empty();
}
final DateTime now = DateTime.now();
final DateTime start = DateTime(now.year, now.month, now.day);
final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999);
final fdc.QueryResult<dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayVariables> result =
await dc.ExampleConnector.instance
.listStaffsApplicationsByBusinessForDay(
businessId: businessId,
dayStart: _toTimestamp(start),
dayEnd: _toTimestamp(end),
)
.execute();
if (result.data.applications.isEmpty) {
return _LiveActivityData.empty();
}
final Map<String, _LiveShiftAggregate> aggregates =
<String, _LiveShiftAggregate>{};
for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
in result.data.applications) {
final String key = '${app.shiftId}:${app.roleId}';
final _LiveShiftAggregate aggregate = aggregates[key] ??
_LiveShiftAggregate(
workersNeeded: app.shiftRole.count,
assigned: app.shiftRole.assigned ?? 0,
cost: app.shiftRole.shift.cost ?? 0,
);
aggregates[key] = aggregate;
}
int totalNeeded = 0;
int totalAssigned = 0;
double totalCost = 0;
for (final _LiveShiftAggregate aggregate in aggregates.values) {
totalNeeded += aggregate.workersNeeded;
totalAssigned += aggregate.assigned;
totalCost += aggregate.cost;
}
int lateCount = 0;
int checkedInCount = 0;
for (final dc.ListStaffsApplicationsByBusinessForDayApplications app
in result.data.applications) {
if (app.checkInTime != null) {
checkedInCount += 1;
}
if (app.status is dc.Known<dc.ApplicationStatus> &&
(app.status as dc.Known<dc.ApplicationStatus>).value ==
dc.ApplicationStatus.LATE) {
lateCount += 1;
}
}
return _LiveActivityData(
totalNeeded: totalNeeded,
totalAssigned: totalAssigned,
totalCost: totalCost,
checkedInCount: checkedInCount,
lateCount: lateCount,
);
}
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final int seconds = dateTime.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(dateTime.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TranslationsClientHomeEn i18n = t.client_home; final TranslationsClientHomeEn i18n = t.client_home;
// Mock data
final List<Map<String, Object>> shifts = <Map<String, Object>>[
<String, Object>{
'workersNeeded': 5,
'filled': 4,
'hourlyRate': 20.0,
'status': 'OPEN',
'date': DateTime.now().toIso8601String().split('T')[0],
},
<String, Object>{
'workersNeeded': 5,
'filled': 5,
'hourlyRate': 22.0,
'status': 'FILLED',
'date': DateTime.now().toIso8601String().split('T')[0],
},
];
final List<Map<String, String>> applications = <Map<String, String>>[
<String, String>{'status': 'CONFIRMED', 'checkInTime': '09:00'},
<String, String>{'status': 'CONFIRMED', 'checkInTime': '09:05'},
<String, String>{'status': 'CONFIRMED'},
<String, String>{'status': 'LATE'},
];
return Column( return Column(
children: <Widget>[ children: <Widget>[
Row( Row(
@@ -51,7 +114,7 @@ class LiveActivityWidget extends StatelessWidget {
), ),
), ),
GestureDetector( GestureDetector(
onTap: onViewAllPressed, onTap: widget.onViewAllPressed,
child: Text( child: Text(
i18n.dashboard.view_all, i18n.dashboard.view_all,
style: UiTypography.footnote1m.copyWith( style: UiTypography.footnote1m.copyWith(
@@ -62,8 +125,82 @@ class LiveActivityWidget extends StatelessWidget {
], ],
), ),
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
CoverageDashboard(shifts: shifts, applications: applications), FutureBuilder<_LiveActivityData>(
future: _liveActivityFuture,
builder: (BuildContext context,
AsyncSnapshot<_LiveActivityData> snapshot) {
final _LiveActivityData data =
snapshot.data ?? _LiveActivityData.empty();
final List<Map<String, Object>> shifts =
<Map<String, Object>>[
<String, Object>{
'workersNeeded': data.totalNeeded,
'filled': data.totalAssigned,
'hourlyRate': data.totalAssigned == 0
? 0.0
: data.totalCost / data.totalAssigned,
'status': 'OPEN',
'date': DateTime.now().toIso8601String().split('T')[0],
},
];
final List<Map<String, Object?>> applications =
<Map<String, Object?>>[];
for (int i = 0; i < data.checkedInCount; i += 1) {
applications.add(
<String, Object?>{
'status': 'CONFIRMED',
'checkInTime': '09:00',
},
);
}
for (int i = 0; i < data.lateCount; i += 1) {
applications.add(<String, Object?>{'status': 'LATE'});
}
return CoverageDashboard(
shifts: shifts,
applications: applications,
);
},
),
], ],
); );
} }
} }
class _LiveActivityData {
const _LiveActivityData({
required this.totalNeeded,
required this.totalAssigned,
required this.totalCost,
required this.checkedInCount,
required this.lateCount,
});
final int totalNeeded;
final int totalAssigned;
final double totalCost;
final int checkedInCount;
final int lateCount;
factory _LiveActivityData.empty() {
return const _LiveActivityData(
totalNeeded: 0,
totalAssigned: 0,
totalCost: 0,
checkedInCount: 0,
lateCount: 0,
);
}
}
class _LiveShiftAggregate {
_LiveShiftAggregate({
required this.workersNeeded,
required this.assigned,
required this.cost,
});
final int workersNeeded;
final int assigned;
final double cost;
}

View File

@@ -356,3 +356,48 @@ query listAcceptedApplicationsByBusinessForDay(
staff { id fullName email phone photoUrl } staff { id fullName email phone photoUrl }
} }
} }
#coverage list and today live
query listStaffsApplicationsByBusinessForDay(
$businessId: UUID!
$dayStart: Timestamp!
$dayEnd: Timestamp!
$offset: Int
$limit: Int
) @auth(level: USER) {
applications(
where: {
status: {in: [ACCEPTED, CONFIRMED, CHECKED_IN, CHECKED_OUT, LATE]}
shift: {
date: { ge: $dayStart, le: $dayEnd }
order: { businessId: { eq: $businessId } }
#status: { eq: ACCEPTED }
}
}
offset: $offset
limit: $limit
orderBy: { appliedAt: ASC }
) {
id
shiftId
roleId
checkInTime
checkOutTime
appliedAt
status
shiftRole{
shift{
location
cost
}
count
assigned
role{
name
}
}
staff { id fullName email phone photoUrl }
}
}

View File

@@ -301,12 +301,14 @@ query listShiftRolesByBusinessAndDateRange(
$end: Timestamp! $end: Timestamp!
$offset: Int $offset: Int
$limit: Int $limit: Int
$status: ShiftStatus
) @auth(level: USER) { ) @auth(level: USER) {
shiftRoles( shiftRoles(
where: { where: {
shift: { shift: {
date: { ge: $start, le: $end } date: { ge: $start, le: $end }
order: { businessId: { eq: $businessId } } order: { businessId: { eq: $businessId } }
status: { eq: $status }
} }
} }
offset: $offset offset: $offset
@@ -462,3 +464,63 @@ query listShiftRolesByBusinessAndDatesSummary(
role { id name } role { id name }
} }
} }
# ------------------------------------------------------------
# BUSINESS: GET COMPLETED SHIFTS FOR A BUSINESS (via order.businessId)
# ------------------------------------------------------------
#for spending insights in home view
query getCompletedShiftsByBusinessId(
$businessId: UUID!
$dateFrom: Timestamp!
$dateTo: Timestamp!
$offset: Int
$limit: Int
) @auth(level: USER) {
shifts(
where: {
order: { businessId: { eq: $businessId } }
status: {in: [IN_PROGRESS, CONFIRMED, COMPLETED, OPEN]}
date: { ge: $dateFrom, le: $dateTo }
}
offset: $offset
limit: $limit
) {
id
#title
#orderId
date
startTime
endTime
hours
cost
#location
#locationAddress
#latitude
#longitude
#description
#status
workersNeeded
filled
#filledAt
#managers
#durationDays
createdAt
order {
#id
#eventName
status
#orderType
#businessId
#vendorId
#business { id businessName email contactName }
#vendor { id companyName }
}
}
}

View File

@@ -163,7 +163,7 @@ query filterStaff(
ownerId ownerId
isRecommended isRecommended
skills skills
industries
backgroundCheckStatus backgroundCheckStatus
employmentType employmentType
initial initial