Whereaboutes and dashboard IT Staff Pulse enhancements
This commit is contained in:
@@ -37,11 +37,19 @@ String _roleLabel(String role) {
|
||||
};
|
||||
}
|
||||
|
||||
/// Threshold after which a live position is considered stale.
|
||||
const _staleDuration = Duration(minutes: 15);
|
||||
|
||||
String _timeAgo(DateTime dt) {
|
||||
final diff = AppTime.now().difference(dt);
|
||||
if (diff.inMinutes < 1) return 'Just now';
|
||||
if (diff.inMinutes < 60) return '${diff.inMinutes}m ago';
|
||||
return '${diff.inHours}h ago';
|
||||
if (diff.inHours < 24) return '${diff.inHours}h ago';
|
||||
return '${diff.inDays}d ago';
|
||||
}
|
||||
|
||||
bool _isStale(DateTime dt) {
|
||||
return AppTime.now().difference(dt) > _staleDuration;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -167,43 +175,49 @@ class _WhereaboutsMap extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Only pin in-premise users — outside-geofence users are greyed out
|
||||
// in the legend and their exact location is not shown on the map.
|
||||
// Show in-premise users on the map; grey out stale positions.
|
||||
final inPremise = positions.where((p) => p.inPremise).toList();
|
||||
|
||||
final markers = inPremise.map((pos) {
|
||||
final profile = profileById[pos.userId];
|
||||
final name = profile?.fullName ?? 'Unknown';
|
||||
final pinColor = _roleColor(profile?.role);
|
||||
final stale = _isStale(pos.updatedAt);
|
||||
final pinColor = stale ? Colors.grey : _roleColor(profile?.role);
|
||||
return Marker(
|
||||
point: LatLng(pos.lat, pos.lng),
|
||||
width: 80,
|
||||
height: 60,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 4,
|
||||
child: Opacity(
|
||||
opacity: stale ? 0.5 : 1.0,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
name.split(' ').first,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: stale
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
),
|
||||
],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
name.split(' ').first,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(Icons.location_pin, size: 28, color: pinColor),
|
||||
],
|
||||
Icon(Icons.location_pin, size: 28, color: pinColor),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
@@ -410,9 +424,10 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
final hasPosition = position != null;
|
||||
final isInPremise = position?.inPremise ?? false;
|
||||
final isTrackingOff = !profile.allowTracking;
|
||||
final isStale = hasPosition && _isStale(position!.updatedAt);
|
||||
|
||||
// Determine display state
|
||||
final bool isActive = hasPosition && isInPremise;
|
||||
final bool isActive = hasPosition && isInPremise && !isStale;
|
||||
final bool isGreyedOut = !isActive;
|
||||
|
||||
// For tracking-off users without a live position, infer from check-in.
|
||||
@@ -428,10 +443,13 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
final Color statusColor;
|
||||
if (isTrackingOff) {
|
||||
if (hasPosition) {
|
||||
final timeLabel = _timeAgo(position!.updatedAt);
|
||||
statusText = isInPremise
|
||||
? 'In premise (Tracking off)'
|
||||
: 'Outside premise (Tracking off)';
|
||||
statusColor = isInPremise ? Colors.green : Colors.grey;
|
||||
? 'In premise (Tracking off) \u00b7 $timeLabel'
|
||||
: 'Outside premise (Tracking off) \u00b7 $timeLabel';
|
||||
statusColor = isStale
|
||||
? Colors.grey
|
||||
: (isInPremise ? Colors.green : Colors.grey);
|
||||
} else if (inferredInPremise) {
|
||||
statusText = 'In premise (Checked in)';
|
||||
statusColor = Colors.green;
|
||||
@@ -439,11 +457,17 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
statusText = 'Tracking off';
|
||||
statusColor = Colors.grey;
|
||||
}
|
||||
} else if (isStale && hasPosition) {
|
||||
final timeLabel = _timeAgo(position!.updatedAt);
|
||||
statusText = isInPremise
|
||||
? 'Last seen in premise \u00b7 $timeLabel'
|
||||
: 'Last seen outside premise \u00b7 $timeLabel';
|
||||
statusColor = Colors.grey;
|
||||
} else if (isActive) {
|
||||
statusText = 'In premise \u00b7 ${_timeAgo(position!.updatedAt)}';
|
||||
statusColor = Colors.green;
|
||||
} else if (hasPosition) {
|
||||
statusText = 'Outside premise';
|
||||
statusText = 'Outside premise \u00b7 ${_timeAgo(position!.updatedAt)}';
|
||||
statusColor = Colors.grey;
|
||||
} else {
|
||||
statusText = 'No location data';
|
||||
@@ -453,6 +477,8 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
final IconData statusIcon;
|
||||
if (isTrackingOff) {
|
||||
statusIcon = Icons.location_disabled;
|
||||
} else if (isStale) {
|
||||
statusIcon = Icons.location_off;
|
||||
} else if (isActive) {
|
||||
statusIcon = Icons.location_on;
|
||||
} else if (hasPosition) {
|
||||
|
||||
Reference in New Issue
Block a user