From b519c49406c13d2914599f07b3170e277b4fef70 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sun, 22 Feb 2026 11:46:38 -0500 Subject: [PATCH] feat: Add orderId and normalized orderType to the Shift model to enable UI grouping and type-badging in shift displays. --- .../shifts_connector_repository_impl.dart | 229 +++++++++++++----- .../home_page/pending_payment_card.dart | 16 +- .../presentation/widgets/my_shift_card.dart | 60 ++--- .../widgets/tabs/find_shifts_tab.dart | 12 +- 4 files changed, 210 insertions(+), 107 deletions(-) diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart b/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart index 6877f1d2..d6f7c2ed 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart @@ -10,9 +10,8 @@ import '../../domain/repositories/shifts_connector_repository.dart'; /// Handles shift-related data operations by interacting with Data Connect. class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { /// Creates a new [ShiftsConnectorRepositoryImpl]. - ShiftsConnectorRepositoryImpl({ - dc.DataConnectService? service, - }) : _service = service ?? dc.DataConnectService.instance; + ShiftsConnectorRepositoryImpl({dc.DataConnectService? service}) + : _service = service ?? dc.DataConnectService.instance; final dc.DataConnectService _service; @@ -23,12 +22,17 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { required DateTime end, }) async { return _service.run(() async { - final dc.GetApplicationsByStaffIdVariablesBuilder query = _service.connector + final dc.GetApplicationsByStaffIdVariablesBuilder query = _service + .connector .getApplicationsByStaffId(staffId: staffId) .dayStart(_service.toTimestamp(start)) .dayEnd(_service.toTimestamp(end)); - final QueryResult response = await query.execute(); + final QueryResult< + dc.GetApplicationsByStaffIdData, + dc.GetApplicationsByStaffIdVariables + > + response = await query.execute(); return _mapApplicationsToShifts(response.data.applications); }); } @@ -45,18 +49,28 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId; if (vendorId == null || vendorId.isEmpty) return []; - final QueryResult response = await _service.connector + final QueryResult< + dc.ListShiftRolesByVendorIdData, + dc.ListShiftRolesByVendorIdVariables + > + response = await _service.connector .listShiftRolesByVendorId(vendorId: vendorId) .execute(); - final List allShiftRoles = response.data.shiftRoles; + final List allShiftRoles = + response.data.shiftRoles; // Fetch current applications to filter out already booked shifts - final QueryResult myAppsResponse = await _service.connector + final QueryResult< + dc.GetApplicationsByStaffIdData, + dc.GetApplicationsByStaffIdVariables + > + myAppsResponse = await _service.connector .getApplicationsByStaffId(staffId: staffId) .execute(); - final Set appliedShiftIds = - myAppsResponse.data.applications.map((dc.GetApplicationsByStaffIdApplications a) => a.shiftId).toSet(); + final Set appliedShiftIds = myAppsResponse.data.applications + .map((dc.GetApplicationsByStaffIdApplications a) => a.shiftId) + .toSet(); final List mappedShifts = []; for (final dc.ListShiftRolesByVendorIdShiftRoles sr in allShiftRoles) { @@ -67,6 +81,12 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { final DateTime? endDt = _service.toDateTime(sr.endTime); final DateTime? createdDt = _service.toDateTime(sr.createdAt); + // Normalise orderType to uppercase for consistent checks in the UI. + // RECURRING → groups shifts into Multi-Day cards. + // PERMANENT → groups shifts into Long Term cards. + final String orderTypeStr = sr.shift.order.orderType.stringValue + .toUpperCase(); + mappedShifts.add( Shift( id: sr.shiftId, @@ -78,7 +98,9 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { location: sr.shift.location ?? '', locationAddress: sr.shift.locationAddress ?? '', date: shiftDate?.toIso8601String() ?? '', - startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', + startTime: startDt != null + ? DateFormat('HH:mm').format(startDt) + : '', endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', createdDate: createdDt?.toIso8601String() ?? '', status: sr.shift.status?.stringValue.toLowerCase() ?? 'open', @@ -88,6 +110,10 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { filledSlots: sr.assigned ?? 0, latitude: sr.shift.latitude, longitude: sr.shift.longitude, + // orderId + orderType power the grouping and type-badge logic in + // FindShiftsTab._groupMultiDayShifts and MyShiftCard._getShiftType. + orderId: sr.shift.orderId, + orderType: orderTypeStr, breakInfo: BreakAdapter.fromData( isPaid: sr.isBreakPaid ?? false, breakTime: sr.breakType?.stringValue, @@ -125,7 +151,8 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { }) async { return _service.run(() async { if (roleId != null && roleId.isNotEmpty) { - final QueryResult roleResult = await _service.connector + final QueryResult + roleResult = await _service.connector .getShiftRoleById(shiftId: shiftId, roleId: roleId) .execute(); final dc.GetShiftRoleByIdShiftRole? sr = roleResult.data.shiftRole; @@ -137,13 +164,22 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { bool hasApplied = false; String status = 'open'; - - final QueryResult appsResponse = await _service.connector + + final QueryResult< + dc.GetApplicationsByStaffIdData, + dc.GetApplicationsByStaffIdVariables + > + appsResponse = await _service.connector .getApplicationsByStaffId(staffId: staffId) .execute(); - - final dc.GetApplicationsByStaffIdApplications? app = appsResponse.data.applications - .where((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId) + + final dc.GetApplicationsByStaffIdApplications? app = appsResponse + .data + .applications + .where( + (dc.GetApplicationsByStaffIdApplications a) => + a.shiftId == shiftId && a.shiftRole.roleId == roleId, + ) .firstOrNull; if (app != null) { @@ -181,7 +217,8 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { ); } - final QueryResult result = await _service.connector.getShiftById(id: shiftId).execute(); + final QueryResult result = + await _service.connector.getShiftById(id: shiftId).execute(); final dc.GetShiftByIdShift? s = result.data.shift; if (s == null) return null; @@ -190,17 +227,23 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { Break? breakInfo; try { - final QueryResult rolesRes = await _service.connector + final QueryResult< + dc.ListShiftRolesByShiftIdData, + dc.ListShiftRolesByShiftIdVariables + > + rolesRes = await _service.connector .listShiftRolesByShiftId(shiftId: shiftId) .execute(); if (rolesRes.data.shiftRoles.isNotEmpty) { required = 0; filled = 0; - for (dc.ListShiftRolesByShiftIdShiftRoles r in rolesRes.data.shiftRoles) { + for (dc.ListShiftRolesByShiftIdShiftRoles r + in rolesRes.data.shiftRoles) { required = (required ?? 0) + r.count; filled = (filled ?? 0) + (r.assigned ?? 0); } - final dc.ListShiftRolesByShiftIdShiftRoles firstRole = rolesRes.data.shiftRoles.first; + final dc.ListShiftRolesByShiftIdShiftRoles firstRole = + rolesRes.data.shiftRoles.first; breakInfo = BreakAdapter.fromData( isPaid: firstRole.isBreakPaid ?? false, breakTime: firstRole.breakType?.stringValue, @@ -247,35 +290,53 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { final String targetRoleId = roleId ?? ''; if (targetRoleId.isEmpty) throw Exception('Missing role id.'); - final QueryResult roleResult = await _service.connector + final QueryResult + roleResult = await _service.connector .getShiftRoleById(shiftId: shiftId, roleId: targetRoleId) .execute(); final dc.GetShiftRoleByIdShiftRole? role = roleResult.data.shiftRole; if (role == null) throw Exception('Shift role not found'); - final QueryResult shiftResult = await _service.connector.getShiftById(id: shiftId).execute(); + final QueryResult + shiftResult = await _service.connector + .getShiftById(id: shiftId) + .execute(); final dc.GetShiftByIdShift? shift = shiftResult.data.shift; if (shift == null) throw Exception('Shift not found'); // Validate daily limit final DateTime? shiftDate = _service.toDateTime(shift.date); if (shiftDate != null) { - final DateTime dayStartUtc = DateTime.utc(shiftDate.year, shiftDate.month, shiftDate.day); - final DateTime dayEndUtc = dayStartUtc.add(const Duration(days: 1)).subtract(const Duration(microseconds: 1)); + final DateTime dayStartUtc = DateTime.utc( + shiftDate.year, + shiftDate.month, + shiftDate.day, + ); + final DateTime dayEndUtc = dayStartUtc + .add(const Duration(days: 1)) + .subtract(const Duration(microseconds: 1)); - final QueryResult validationResponse = await _service.connector + final QueryResult< + dc.VaidateDayStaffApplicationData, + dc.VaidateDayStaffApplicationVariables + > + validationResponse = await _service.connector .vaidateDayStaffApplication(staffId: staffId) .dayStart(_service.toTimestamp(dayStartUtc)) .dayEnd(_service.toTimestamp(dayEndUtc)) .execute(); - + if (validationResponse.data.applications.isNotEmpty) { throw Exception('The user already has a shift that day.'); } } // Check for existing application - final QueryResult existingAppRes = await _service.connector + final QueryResult< + dc.GetApplicationByStaffShiftAndRoleData, + dc.GetApplicationByStaffShiftAndRoleVariables + > + existingAppRes = await _service.connector .getApplicationByStaffShiftAndRole( staffId: staffId, shiftId: shiftId, @@ -295,14 +356,20 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { String? createdAppId; try { - final OperationResult createRes = await _service.connector.createApplication( - shiftId: shiftId, - staffId: staffId, - roleId: targetRoleId, - status: dc.ApplicationStatus.CONFIRMED, // Matches existing logic - origin: dc.ApplicationOrigin.STAFF, - ).execute(); - + final OperationResult< + dc.CreateApplicationData, + dc.CreateApplicationVariables + > + createRes = await _service.connector + .createApplication( + shiftId: shiftId, + staffId: staffId, + roleId: targetRoleId, + status: dc.ApplicationStatus.CONFIRMED, // Matches existing logic + origin: dc.ApplicationOrigin.STAFF, + ) + .execute(); + createdAppId = createRes.data.application_insert.id; await _service.connector @@ -317,7 +384,9 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { } catch (e) { // Simple rollback attempt (not guaranteed) if (createdAppId != null) { - await _service.connector.deleteApplication(id: createdAppId).execute(); + await _service.connector + .deleteApplication(id: createdAppId) + .execute(); } rethrow; } @@ -325,11 +394,12 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { } @override - Future acceptShift({ - required String shiftId, - required String staffId, - }) { - return _updateApplicationStatus(shiftId, staffId, dc.ApplicationStatus.CONFIRMED); + Future acceptShift({required String shiftId, required String staffId}) { + return _updateApplicationStatus( + shiftId, + staffId, + dc.ApplicationStatus.CONFIRMED, + ); } @override @@ -337,7 +407,11 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { required String shiftId, required String staffId, }) { - return _updateApplicationStatus(shiftId, staffId, dc.ApplicationStatus.REJECTED); + return _updateApplicationStatus( + shiftId, + staffId, + dc.ApplicationStatus.REJECTED, + ); } @override @@ -351,18 +425,24 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { @override Future> getHistoryShifts({required String staffId}) async { return _service.run(() async { - final QueryResult response = await _service.connector + final QueryResult< + dc.ListCompletedApplicationsByStaffIdData, + dc.ListCompletedApplicationsByStaffIdVariables + > + response = await _service.connector .listCompletedApplicationsByStaffId(staffId: staffId) .execute(); - + final List shifts = []; - for (final dc.ListCompletedApplicationsByStaffIdApplications app in response.data.applications) { + for (final dc.ListCompletedApplicationsByStaffIdApplications app + in response.data.applications) { final String roleName = app.shiftRole.role.name; - final String orderName = (app.shift.order.eventName ?? '').trim().isNotEmpty + final String orderName = + (app.shift.order.eventName ?? '').trim().isNotEmpty ? app.shift.order.eventName! : app.shift.order.business.businessName; final String title = '$roleName - $orderName'; - + final DateTime? shiftDate = _service.toDateTime(app.shift.date); final DateTime? startDt = _service.toDateTime(app.shiftRole.startTime); final DateTime? endDt = _service.toDateTime(app.shiftRole.endTime); @@ -379,7 +459,9 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { location: app.shift.location ?? '', locationAddress: app.shift.order.teamHub.hubName, date: shiftDate?.toIso8601String() ?? '', - startTime: startDt != null ? DateFormat('HH:mm').format(startDt) : '', + startTime: startDt != null + ? DateFormat('HH:mm').format(startDt) + : '', endTime: endDt != null ? DateFormat('HH:mm').format(endDt) : '', createdDate: createdDt?.toIso8601String() ?? '', status: 'completed', // Hardcoded as checked out implies completion @@ -406,7 +488,8 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { List _mapApplicationsToShifts(List apps) { return apps.map((app) { final String roleName = app.shiftRole.role.name; - final String orderName = (app.shift.order.eventName ?? '').trim().isNotEmpty + final String orderName = + (app.shift.order.eventName ?? '').trim().isNotEmpty ? app.shift.order.eventName! : app.shift.order.business.businessName; final String title = '$roleName - $orderName'; @@ -418,7 +501,7 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { final bool hasCheckIn = app.checkInTime != null; final bool hasCheckOut = app.checkOutTime != null; - + String status; if (hasCheckOut) { status = 'completed'; @@ -479,12 +562,20 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { ) async { return _service.run(() async { // First try to find the application - final QueryResult appsResponse = await _service.connector + final QueryResult< + dc.GetApplicationsByStaffIdData, + dc.GetApplicationsByStaffIdVariables + > + appsResponse = await _service.connector .getApplicationsByStaffId(staffId: staffId) .execute(); - - final dc.GetApplicationsByStaffIdApplications? app = appsResponse.data.applications - .where((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId) + + final dc.GetApplicationsByStaffIdApplications? app = appsResponse + .data + .applications + .where( + (dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId, + ) .firstOrNull; if (app != null) { @@ -494,19 +585,26 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { .execute(); } else if (newStatus == dc.ApplicationStatus.REJECTED) { // If declining but no app found, create a rejected application - final QueryResult rolesRes = await _service.connector + final QueryResult< + dc.ListShiftRolesByShiftIdData, + dc.ListShiftRolesByShiftIdVariables + > + rolesRes = await _service.connector .listShiftRolesByShiftId(shiftId: shiftId) .execute(); - + if (rolesRes.data.shiftRoles.isNotEmpty) { - final dc.ListShiftRolesByShiftIdShiftRoles firstRole = rolesRes.data.shiftRoles.first; - await _service.connector.createApplication( - shiftId: shiftId, - staffId: staffId, - roleId: firstRole.id, - status: dc.ApplicationStatus.REJECTED, - origin: dc.ApplicationOrigin.STAFF, - ).execute(); + final dc.ListShiftRolesByShiftIdShiftRoles firstRole = + rolesRes.data.shiftRoles.first; + await _service.connector + .createApplication( + shiftId: shiftId, + staffId: staffId, + roleId: firstRole.id, + status: dc.ApplicationStatus.REJECTED, + origin: dc.ApplicationOrigin.STAFF, + ) + .execute(); } } else { throw Exception("Application not found for shift $shiftId"); @@ -514,4 +612,3 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { }); } } - diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart index 4476aecc..77fe1ff1 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/widgets/home_page/pending_payment_card.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; - import 'package:design_system/design_system.dart'; import 'package:core_localization/core_localization.dart'; import 'package:krow_core/core.dart'; - /// Card widget for displaying pending payment information, using design system tokens. class PendingPaymentCard extends StatelessWidget { /// Creates a [PendingPaymentCard]. @@ -21,7 +19,10 @@ class PendingPaymentCard extends StatelessWidget { padding: const EdgeInsets.all(UiConstants.space4), decoration: BoxDecoration( gradient: LinearGradient( - colors: [UiColors.primary.withOpacity(0.08), UiColors.primary.withOpacity(0.04)], + colors: [ + UiColors.primary.withOpacity(0.08), + UiColors.primary.withOpacity(0.04), + ], begin: Alignment.centerLeft, end: Alignment.centerRight, ), @@ -59,7 +60,9 @@ class PendingPaymentCard extends StatelessWidget { ), Text( pendingI18n.subtitle, - style: UiTypography.body3r.copyWith(color: UiColors.mutedForeground), + style: UiTypography.body3r.copyWith( + color: UiColors.mutedForeground, + ), overflow: TextOverflow.ellipsis, ), ], @@ -70,10 +73,7 @@ class PendingPaymentCard extends StatelessWidget { ), Row( children: [ - Text( - '\$285.00', - style: UiTypography.headline4m, - ), + Text('\$285.00', style: UiTypography.headline4m), SizedBox(width: UiConstants.space2), Icon( UiIcons.chevronRight, diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart index 37373e51..37cafb9c 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/my_shift_card.dart @@ -185,12 +185,15 @@ class _MyShiftCardState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status Badge - if (statusText.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: UiConstants.space2), - child: Row( - children: [ + // Badge row: shows the status label and the shift-type chip. + // The type chip (One Day / Multi-Day / Long Term) is always + // rendered when orderType is present — even for "open" find-shifts + // cards that may have no meaningful status text. + Padding( + padding: const EdgeInsets.only(bottom: UiConstants.space2), + child: Row( + children: [ + if (statusText.isNotEmpty) ...[ if (statusIcon != null) Padding( padding: const EdgeInsets.only( @@ -221,30 +224,31 @@ class _MyShiftCardState extends State { letterSpacing: 0.5, ), ), - // Shift Type Badge (Order type) - if ((widget.shift.orderType ?? '').isNotEmpty) ...[ - const SizedBox(width: UiConstants.space2), - Container( - padding: const EdgeInsets.symmetric( - horizontal: UiConstants.space2, - vertical: 2, - ), - decoration: BoxDecoration( - color: UiColors.background, - borderRadius: UiConstants.radiusSm, - border: Border.all(color: UiColors.border), - ), - child: Text( - _getShiftType(), - style: UiTypography.footnote2m.copyWith( - color: UiColors.textSecondary, - ), - ), - ), - ], + const SizedBox(width: UiConstants.space2), ], - ), + // Type badge — driven by RECURRING / PERMANENT / one-day + // order data and always visible so users can filter + // Find Shifts cards at a glance. + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: UiColors.background, + borderRadius: UiConstants.radiusSm, + border: Border.all(color: UiColors.border), + ), + child: Text( + _getShiftType(), + style: UiTypography.footnote2m.copyWith( + color: UiColors.textSecondary, + ), + ), + ), + ], ), + ), Row( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart index 81e6ac03..863b2afe 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart @@ -85,11 +85,13 @@ class _FindShiftsTabState extends State { final Shift first = group.first; final List schedules = group - .map((s) => ShiftSchedule( - date: s.date, - startTime: s.startTime, - endTime: s.endTime, - )) + .map( + (s) => ShiftSchedule( + date: s.date, + startTime: s.startTime, + endTime: s.endTime, + ), + ) .toList(); result.add(