feat(shift): enhance Shift entity with client and role names, update JSON deserialization

This commit is contained in:
Achintha Isuru
2026-03-18 17:56:48 -04:00
parent f5699865f9
commit baf426935e
3 changed files with 69 additions and 35 deletions

View File

@@ -28,19 +28,36 @@ class Shift extends Equatable {
this.clockInMode, this.clockInMode,
this.allowClockInOverride, this.allowClockInOverride,
this.nfcTagId, this.nfcTagId,
this.clientName,
this.roleName,
}); });
/// Deserialises from the V2 API JSON response. /// Deserialises from the V2 API JSON response.
///
/// Supports both the standard shift JSON shape (`id`, `startsAt`, `endsAt`)
/// and the today-shifts endpoint shape (`shiftId`, `startTime`, `endTime`).
factory Shift.fromJson(Map<String, dynamic> json) { factory Shift.fromJson(Map<String, dynamic> json) {
final String? clientName = json['clientName'] as String?;
final String? roleName = json['roleName'] as String?;
return Shift( return Shift(
id: json['id'] as String, id: json['id'] as String? ?? json['shiftId'] as String,
orderId: json['orderId'] as String?, orderId: json['orderId'] as String?,
title: json['title'] as String? ?? '', title: json['title'] as String? ??
roleName ??
clientName ??
'',
status: ShiftStatus.fromJson(json['status'] as String?), status: ShiftStatus.fromJson(json['status'] as String?),
startsAt: DateTime.parse(json['startsAt'] as String), startsAt: DateTime.parse(
endsAt: DateTime.parse(json['endsAt'] as String), json['startsAt'] as String? ?? json['startTime'] as String,
),
endsAt: DateTime.parse(
json['endsAt'] as String? ?? json['endTime'] as String,
),
timezone: json['timezone'] as String? ?? 'UTC', timezone: json['timezone'] as String? ?? 'UTC',
locationName: json['locationName'] as String?, locationName: json['locationName'] as String? ??
json['locationAddress'] as String? ??
json['location'] as String?,
locationAddress: json['locationAddress'] as String?, locationAddress: json['locationAddress'] as String?,
latitude: parseDouble(json['latitude']), latitude: parseDouble(json['latitude']),
longitude: parseDouble(json['longitude']), longitude: parseDouble(json['longitude']),
@@ -51,6 +68,8 @@ class Shift extends Equatable {
clockInMode: json['clockInMode'] as String?, clockInMode: json['clockInMode'] as String?,
allowClockInOverride: json['allowClockInOverride'] as bool?, allowClockInOverride: json['allowClockInOverride'] as bool?,
nfcTagId: json['nfcTagId'] as String?, nfcTagId: json['nfcTagId'] as String?,
clientName: clientName,
roleName: roleName,
); );
} }
@@ -108,6 +127,12 @@ class Shift extends Equatable {
/// NFC tag identifier for NFC-based clock-in. /// NFC tag identifier for NFC-based clock-in.
final String? nfcTagId; final String? nfcTagId;
/// Name of the client (business) this shift belongs to.
final String? clientName;
/// Name of the role the worker is assigned for this shift.
final String? roleName;
/// Serialises to JSON. /// Serialises to JSON.
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return <String, dynamic>{ return <String, dynamic>{
@@ -129,6 +154,8 @@ class Shift extends Equatable {
'clockInMode': clockInMode, 'clockInMode': clockInMode,
'allowClockInOverride': allowClockInOverride, 'allowClockInOverride': allowClockInOverride,
'nfcTagId': nfcTagId, 'nfcTagId': nfcTagId,
'clientName': clientName,
'roleName': roleName,
}; };
} }
@@ -161,5 +188,7 @@ class Shift extends Equatable {
clockInMode, clockInMode,
allowClockInOverride, allowClockInOverride,
nfcTagId, nfcTagId,
clientName,
roleName,
]; ];
} }

View File

@@ -26,7 +26,7 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
return items return items
.map( .map(
(dynamic json) => (dynamic json) =>
_mapTodayShiftJsonToShift(json as Map<String, dynamic>), Shift.fromJson(json as Map<String, dynamic>),
) )
.toList(); .toList();
} }
@@ -58,26 +58,4 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
// Re-fetch the attendance status to get the canonical state after clock-out. // Re-fetch the attendance status to get the canonical state after clock-out.
return getAttendanceStatus(); return getAttendanceStatus();
} }
/// Maps a V2 `listTodayShifts` JSON item to the domain [Shift] entity.
static Shift _mapTodayShiftJsonToShift(Map<String, dynamic> json) {
return Shift(
id: json['shiftId'] as String,
orderId: null,
title: json['clientName'] as String? ?? json['roleName'] as String? ?? '',
status: ShiftStatus.assigned,
startsAt: DateTime.parse(json['startTime'] as String),
endsAt: DateTime.parse(json['endTime'] as String),
locationName: json['locationAddress'] as String? ??
json['location'] as String?,
latitude: Shift.parseDouble(json['latitude']),
longitude: Shift.parseDouble(json['longitude']),
geofenceRadiusMeters: json['geofenceRadiusMeters'] as int?,
clockInMode: json['clockInMode'] as String?,
allowClockInOverride: json['allowClockInOverride'] as bool?,
nfcTagId: json['nfcTagId'] as String?,
requiredWorkers: 0,
assignedWorkers: 0,
);
}
} }

View File

@@ -48,7 +48,13 @@ class ShiftCard extends StatelessWidget {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded(child: _ShiftDetails(shift: shift, isSelected: isSelected, i18n: i18n)), Expanded(
child: _ShiftDetails(
shift: shift,
isSelected: isSelected,
i18n: i18n,
),
),
_ShiftTimeRange(shift: shift), _ShiftTimeRange(shift: shift),
], ],
), ),
@@ -76,15 +82,36 @@ class _ShiftDetails extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final String displayTitle = shift.roleName ?? shift.title;
final String? displaySubtitle = shift.clientName;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(shift.title, style: UiTypography.body2b), Text(displayTitle, style: UiTypography.body2b),
// Currently showing locationName as subtitle fallback. if (displaySubtitle != null && displaySubtitle.isNotEmpty)
Text( Text(displaySubtitle, style: UiTypography.body3r.textSecondary),
shift.locationName ?? '', if (shift.locationName != null && shift.locationName!.isNotEmpty)
style: UiTypography.body3r.textSecondary, Padding(
), padding: const EdgeInsets.only(top: UiConstants.space1),
child: Row(
children: <Widget>[
const Icon(
UiIcons.mapPin,
size: 14,
color: UiColors.textSecondary,
),
const SizedBox(width: UiConstants.space1),
Expanded(
child: Text(
shift.locationName!,
style: UiTypography.body3r.textSecondary,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
], ],
); );
} }