live today from home

This commit is contained in:
José Salazar
2026-01-27 15:43:24 -05:00
parent 4cdbebf2c0
commit e38c13e61a
7 changed files with 17279 additions and 17117 deletions

View File

@@ -1,16 +1,16 @@
# Basic Usage # Basic Usage
```dart ```dart
ExampleConnector.instance.listClientFeedbacks(listClientFeedbacksVariables).execute(); ExampleConnector.instance.listActivityLogs(listActivityLogsVariables).execute();
ExampleConnector.instance.getClientFeedbackById(getClientFeedbackByIdVariables).execute(); ExampleConnector.instance.getActivityLogById(getActivityLogByIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByBusinessId(listClientFeedbacksByBusinessIdVariables).execute(); ExampleConnector.instance.listActivityLogsByUserId(listActivityLogsByUserIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByVendorId(listClientFeedbacksByVendorIdVariables).execute(); ExampleConnector.instance.listUnreadActivityLogsByUserId(listUnreadActivityLogsByUserIdVariables).execute();
ExampleConnector.instance.listClientFeedbacksByBusinessAndVendor(listClientFeedbacksByBusinessAndVendorVariables).execute(); ExampleConnector.instance.filterActivityLogs(filterActivityLogsVariables).execute();
ExampleConnector.instance.filterClientFeedbacks(filterClientFeedbacksVariables).execute(); ExampleConnector.instance.listConversations(listConversationsVariables).execute();
ExampleConnector.instance.listClientFeedbackRatingsByVendorId(listClientFeedbackRatingsByVendorIdVariables).execute(); ExampleConnector.instance.getConversationById(getConversationByIdVariables).execute();
ExampleConnector.instance.createStaffAvailabilityStats(createStaffAvailabilityStatsVariables).execute(); ExampleConnector.instance.listConversationsByType(listConversationsByTypeVariables).execute();
ExampleConnector.instance.updateStaffAvailabilityStats(updateStaffAvailabilityStatsVariables).execute(); ExampleConnector.instance.listConversationsByStatus(listConversationsByStatusVariables).execute();
ExampleConnector.instance.deleteStaffAvailabilityStats(deleteStaffAvailabilityStatsVariables).execute(); ExampleConnector.instance.filterConversations(filterConversationsVariables).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.listWorkforceByStaffId({ ... }) await ExampleConnector.instance.createStaffRole({ ... })
.offset(...) .roleType(...)
.execute(); .execute();
``` ```

View File

@@ -114,10 +114,14 @@ class ListStaffsApplicationsByBusinessForDayApplications {
@immutable @immutable
class ListStaffsApplicationsByBusinessForDayApplicationsShiftRole { class ListStaffsApplicationsByBusinessForDayApplicationsShiftRole {
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift shift; final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift shift;
final int count;
final int? assigned;
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole role; final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole role;
ListStaffsApplicationsByBusinessForDayApplicationsShiftRole.fromJson(dynamic json): ListStaffsApplicationsByBusinessForDayApplicationsShiftRole.fromJson(dynamic json):
shift = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(json['shift']), shift = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(json['shift']),
count = nativeFromJson<int>(json['count']),
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
role = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole.fromJson(json['role']); role = ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleRole.fromJson(json['role']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -130,22 +134,30 @@ class ListStaffsApplicationsByBusinessForDayApplicationsShiftRole {
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRole otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRole; final ListStaffsApplicationsByBusinessForDayApplicationsShiftRole otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRole;
return shift == otherTyped.shift && return shift == otherTyped.shift &&
count == otherTyped.count &&
assigned == otherTyped.assigned &&
role == otherTyped.role; role == otherTyped.role;
} }
@override @override
int get hashCode => Object.hashAll([shift.hashCode, role.hashCode]); int get hashCode => Object.hashAll([shift.hashCode, count.hashCode, assigned.hashCode, role.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
Map<String, dynamic> json = {}; Map<String, dynamic> json = {};
json['shift'] = shift.toJson(); json['shift'] = shift.toJson();
json['count'] = nativeToJson<int>(count);
if (assigned != null) {
json['assigned'] = nativeToJson<int?>(assigned);
}
json['role'] = role.toJson(); json['role'] = role.toJson();
return json; return json;
} }
ListStaffsApplicationsByBusinessForDayApplicationsShiftRole({ ListStaffsApplicationsByBusinessForDayApplicationsShiftRole({
required this.shift, required this.shift,
required this.count,
this.assigned,
required this.role, required this.role,
}); });
} }
@@ -153,9 +165,11 @@ class ListStaffsApplicationsByBusinessForDayApplicationsShiftRole {
@immutable @immutable
class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift { class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift {
final String? location; final String? location;
final double? cost;
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(dynamic json): ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift.fromJson(dynamic json):
location = json['location'] == null ? null : nativeFromJson<String>(json['location']); location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
cost = json['cost'] == null ? null : nativeFromJson<double>(json['cost']);
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if(identical(this, other)) { if(identical(this, other)) {
@@ -166,11 +180,12 @@ class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift {
} }
final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift; final ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift otherTyped = other as ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift;
return location == otherTyped.location; return location == otherTyped.location &&
cost == otherTyped.cost;
} }
@override @override
int get hashCode => location.hashCode; int get hashCode => Object.hashAll([location.hashCode, cost.hashCode]);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -178,11 +193,15 @@ class ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift {
if (location != null) { if (location != null) {
json['location'] = nativeToJson<String?>(location); json['location'] = nativeToJson<String?>(location);
} }
if (cost != null) {
json['cost'] = nativeToJson<double?>(cost);
}
return json; return json;
} }
ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift({ ListStaffsApplicationsByBusinessForDayApplicationsShiftRoleShift({
this.location, this.location,
this.cost,
}); });
} }

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

@@ -357,7 +357,7 @@ query listAcceptedApplicationsByBusinessForDay(
} }
} }
#coverage list #coverage list and today live
query listStaffsApplicationsByBusinessForDay( query listStaffsApplicationsByBusinessForDay(
$businessId: UUID! $businessId: UUID!
$dayStart: Timestamp! $dayStart: Timestamp!
@@ -385,10 +385,14 @@ query listStaffsApplicationsByBusinessForDay(
checkOutTime checkOutTime
appliedAt appliedAt
status status
shiftRole{ shiftRole{
shift{ shift{
location location
cost
} }
count
assigned
role{ role{
name name