live today from home
This commit is contained in:
@@ -94,7 +94,9 @@ class DashboardWidgetBuilder extends StatelessWidget {
|
||||
: 0,
|
||||
);
|
||||
case 'liveActivity':
|
||||
return LiveActivityWidget(onViewAllPressed: () {});
|
||||
return LiveActivityWidget(
|
||||
onViewAllPressed: () => Modular.to.navigate('/client-main/coverage/'),
|
||||
);
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@@ -1,44 +1,107 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.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';
|
||||
|
||||
/// A widget that displays live activity information.
|
||||
class LiveActivityWidget extends StatelessWidget {
|
||||
class LiveActivityWidget extends StatefulWidget {
|
||||
/// Callback when "View all" is pressed.
|
||||
final VoidCallback onViewAllPressed;
|
||||
|
||||
/// Creates a [LiveActivityWidget].
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
@@ -51,7 +114,7 @@ class LiveActivityWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onViewAllPressed,
|
||||
onTap: widget.onViewAllPressed,
|
||||
child: Text(
|
||||
i18n.dashboard.view_all,
|
||||
style: UiTypography.footnote1m.copyWith(
|
||||
@@ -62,8 +125,82 @@ class LiveActivityWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user