diff --git a/apps/mobile/packages/core/lib/src/services/api_service/inspectors/auth_interceptor.dart b/apps/mobile/packages/core/lib/src/services/api_service/inspectors/auth_interceptor.dart index 2d094d7c..bd1020bd 100644 --- a/apps/mobile/packages/core/lib/src/services/api_service/inspectors/auth_interceptor.dart +++ b/apps/mobile/packages/core/lib/src/services/api_service/inspectors/auth_interceptor.dart @@ -32,7 +32,7 @@ class AuthInterceptor extends Interceptor { if (!skipAuth) { final User? user = FirebaseAuth.instance.currentUser; if (user != null) { - final String? token = await user.getIdToken(); + final String? token = await user.getIdToken(); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } diff --git a/apps/mobile/packages/core/lib/src/utils/date_time_utils.dart b/apps/mobile/packages/core/lib/src/utils/date_time_utils.dart index 1d142b33..d18d076d 100644 --- a/apps/mobile/packages/core/lib/src/utils/date_time_utils.dart +++ b/apps/mobile/packages/core/lib/src/utils/date_time_utils.dart @@ -4,4 +4,9 @@ class DateTimeUtils { static DateTime toDeviceTime(DateTime date) { return date.toLocal(); } + + /// Converts a local [DateTime] back to UTC for API payloads. + static String toUtcIso(DateTime local) { + return local.toUtc().toIso8601String(); + } } diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index c772ba45..c1f7814f 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -25,6 +25,9 @@ export 'src/entities/enums/staff_skill.dart'; export 'src/entities/enums/staff_status.dart'; export 'src/entities/enums/user_role.dart'; +// Utils +export 'src/core/utils/utc_parser.dart'; + // Core export 'src/core/services/api_services/api_endpoint.dart'; export 'src/core/services/api_services/api_response.dart'; diff --git a/apps/mobile/packages/domain/lib/src/core/utils/utc_parser.dart b/apps/mobile/packages/domain/lib/src/core/utils/utc_parser.dart new file mode 100644 index 00000000..8ec3572e --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/core/utils/utc_parser.dart @@ -0,0 +1,6 @@ +/// Parses a UTC ISO 8601 timestamp and converts to local device time. +DateTime parseUtcToLocal(String value) => DateTime.parse(value).toLocal(); + +/// Parses a nullable UTC ISO 8601 timestamp. Returns null if input is null. +DateTime? tryParseUtcToLocal(String? value) => + value != null ? DateTime.parse(value).toLocal() : null; diff --git a/apps/mobile/packages/domain/lib/src/entities/benefits/benefit_history.dart b/apps/mobile/packages/domain/lib/src/entities/benefits/benefit_history.dart index f9933a37..6ca1629d 100644 --- a/apps/mobile/packages/domain/lib/src/entities/benefits/benefit_history.dart +++ b/apps/mobile/packages/domain/lib/src/entities/benefits/benefit_history.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/benefit_status.dart'; +import '../../core/utils/utc_parser.dart'; + /// A historical record of a staff benefit accrual period. /// /// Returned by `GET /staff/profile/benefits/history`. @@ -28,10 +30,8 @@ class BenefitHistory extends Equatable { benefitType: json['benefitType'] as String, title: json['title'] as String, status: BenefitStatus.fromJson(json['status'] as String?), - effectiveAt: DateTime.parse(json['effectiveAt'] as String), - endedAt: json['endedAt'] != null - ? DateTime.parse(json['endedAt'] as String) - : null, + effectiveAt: parseUtcToLocal(json['effectiveAt'] as String), + endedAt: tryParseUtcToLocal(json['endedAt'] as String?), trackedHours: (json['trackedHours'] as num).toInt(), targetHours: (json['targetHours'] as num).toInt(), notes: json['notes'] as String?, diff --git a/apps/mobile/packages/domain/lib/src/entities/business/business.dart b/apps/mobile/packages/domain/lib/src/entities/business/business.dart index 36339f32..2c658828 100644 --- a/apps/mobile/packages/domain/lib/src/entities/business/business.dart +++ b/apps/mobile/packages/domain/lib/src/entities/business/business.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/business_status.dart'; +import '../../core/utils/utc_parser.dart'; + /// A client company registered on the platform. /// /// Maps to the V2 `businesses` table. @@ -35,12 +37,8 @@ class Business extends Equatable { metadata: json['metadata'] is Map ? Map.from(json['metadata'] as Map) : const {}, - createdAt: json['createdAt'] != null - ? DateTime.parse(json['createdAt'] as String) - : null, - updatedAt: json['updatedAt'] != null - ? DateTime.parse(json['updatedAt'] as String) - : null, + createdAt: tryParseUtcToLocal(json['createdAt'] as String?), + updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/clock_in/attendance_status.dart b/apps/mobile/packages/domain/lib/src/entities/clock_in/attendance_status.dart index 043179d7..b71f28e7 100644 --- a/apps/mobile/packages/domain/lib/src/entities/clock_in/attendance_status.dart +++ b/apps/mobile/packages/domain/lib/src/entities/clock_in/attendance_status.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/attendance_status_type.dart'; +import '../../core/utils/utc_parser.dart'; + /// Current clock-in / attendance status of the staff member. /// /// Returned by `GET /staff/clock-in/status`. When no open session exists @@ -20,9 +22,7 @@ class AttendanceStatus extends Equatable { activeShiftId: json['activeShiftId'] as String?, attendanceStatus: AttendanceStatusType.fromJson(json['attendanceStatus'] as String?), - clockInAt: json['clockInAt'] != null - ? DateTime.parse(json['clockInAt'] as String) - : null, + clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/assigned_worker.dart b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/assigned_worker.dart index a16e8a41..a0d82248 100644 --- a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/assigned_worker.dart +++ b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/assigned_worker.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/assignment_status.dart'; +import '../../core/utils/utc_parser.dart'; + /// A worker assigned to a coverage shift. /// /// Nested within [ShiftWithWorkers]. @@ -23,9 +25,7 @@ class AssignedWorker extends Equatable { staffId: json['staffId'] as String, fullName: json['fullName'] as String, status: AssignmentStatus.fromJson(json['status'] as String?), - checkInAt: json['checkInAt'] != null - ? DateTime.parse(json['checkInAt'] as String) - : null, + checkInAt: tryParseUtcToLocal(json['checkInAt'] as String?), hasReview: json['hasReview'] as bool? ?? false, ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/time_range.dart b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/time_range.dart index 543deccd..144d8194 100644 --- a/apps/mobile/packages/domain/lib/src/entities/coverage_domain/time_range.dart +++ b/apps/mobile/packages/domain/lib/src/entities/coverage_domain/time_range.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// A time range with start and end timestamps. /// /// Used within [ShiftWithWorkers] for shift time windows. @@ -13,8 +15,8 @@ class TimeRange extends Equatable { /// Deserialises a [TimeRange] from a V2 API JSON map. factory TimeRange.fromJson(Map json) { return TimeRange( - startsAt: DateTime.parse(json['startsAt'] as String), - endsAt: DateTime.parse(json['endsAt'] as String), + startsAt: parseUtcToLocal(json['startsAt'] as String), + endsAt: parseUtcToLocal(json['endsAt'] as String), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/financial/invoice.dart b/apps/mobile/packages/domain/lib/src/entities/financial/invoice.dart index 7d370fd3..27afa489 100644 --- a/apps/mobile/packages/domain/lib/src/entities/financial/invoice.dart +++ b/apps/mobile/packages/domain/lib/src/entities/financial/invoice.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/invoice_status.dart'; +import '../../core/utils/utc_parser.dart'; + /// An invoice issued to a business for services rendered. /// /// Returned by `GET /client/billing/invoices/*`. @@ -25,12 +27,8 @@ class Invoice extends Equatable { invoiceNumber: json['invoiceNumber'] as String, amountCents: (json['amountCents'] as num).toInt(), status: InvoiceStatus.fromJson(json['status'] as String?), - dueDate: json['dueDate'] != null - ? DateTime.parse(json['dueDate'] as String) - : null, - paymentDate: json['paymentDate'] != null - ? DateTime.parse(json['paymentDate'] as String) - : null, + dueDate: tryParseUtcToLocal(json['dueDate'] as String?), + paymentDate: tryParseUtcToLocal(json['paymentDate'] as String?), vendorId: json['vendorId'] as String?, vendorName: json['vendorName'] as String?, ); diff --git a/apps/mobile/packages/domain/lib/src/entities/financial/payment_chart_point.dart b/apps/mobile/packages/domain/lib/src/entities/financial/payment_chart_point.dart index 2e9f92f0..ac5dfbc5 100644 --- a/apps/mobile/packages/domain/lib/src/entities/financial/payment_chart_point.dart +++ b/apps/mobile/packages/domain/lib/src/entities/financial/payment_chart_point.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// A single data point in the staff payment chart. /// /// Returned by `GET /staff/payments/chart`. @@ -13,7 +15,7 @@ class PaymentChartPoint extends Equatable { /// Deserialises a [PaymentChartPoint] from a V2 API JSON map. factory PaymentChartPoint.fromJson(Map json) { return PaymentChartPoint( - bucket: DateTime.parse(json['bucket'] as String), + bucket: parseUtcToLocal(json['bucket'] as String), amountCents: (json['amountCents'] as num).toInt(), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/financial/staff_payment.dart b/apps/mobile/packages/domain/lib/src/entities/financial/staff_payment.dart index 3df1b383..159a7ef3 100644 --- a/apps/mobile/packages/domain/lib/src/entities/financial/staff_payment.dart +++ b/apps/mobile/packages/domain/lib/src/entities/financial/staff_payment.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/payment_status.dart'; +import '../../core/utils/utc_parser.dart'; + /// A single payment record for a staff member. /// /// Returned by `GET /staff/payments/history`. @@ -23,7 +25,7 @@ class PaymentRecord extends Equatable { return PaymentRecord( paymentId: json['paymentId'] as String, amountCents: (json['amountCents'] as num).toInt(), - date: DateTime.parse(json['date'] as String), + date: parseUtcToLocal(json['date'] as String), status: PaymentStatus.fromJson(json['status'] as String?), shiftName: json['shiftName'] as String?, location: json['location'] as String?, diff --git a/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart b/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart index a5d459ec..2447d686 100644 --- a/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart +++ b/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// A single time-card entry for a completed shift. /// /// Returned by `GET /staff/profile/time-card`. @@ -19,15 +21,11 @@ class TimeCardEntry extends Equatable { /// Deserialises a [TimeCardEntry] from a V2 API JSON map. factory TimeCardEntry.fromJson(Map json) { return TimeCardEntry( - date: DateTime.parse(json['date'] as String), + date: parseUtcToLocal(json['date'] as String), shiftName: json['shiftName'] as String, location: json['location'] as String?, - clockInAt: json['clockInAt'] != null - ? DateTime.parse(json['clockInAt'] as String) - : null, - clockOutAt: json['clockOutAt'] != null - ? DateTime.parse(json['clockOutAt'] as String) - : null, + clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?), + clockOutAt: tryParseUtcToLocal(json['clockOutAt'] as String?), minutesWorked: (json['minutesWorked'] as num).toInt(), hourlyRateCents: json['hourlyRateCents'] != null ? (json['hourlyRateCents'] as num).toInt() diff --git a/apps/mobile/packages/domain/lib/src/entities/orders/order_item.dart b/apps/mobile/packages/domain/lib/src/entities/orders/order_item.dart index 473495ca..dfcd6072 100644 --- a/apps/mobile/packages/domain/lib/src/entities/orders/order_item.dart +++ b/apps/mobile/packages/domain/lib/src/entities/orders/order_item.dart @@ -3,6 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart'; import 'package:krow_domain/src/entities/enums/shift_status.dart'; +import '../../core/utils/utc_parser.dart'; import 'assigned_worker_summary.dart'; /// A line item within an order, representing a role needed for a shift. @@ -42,9 +43,9 @@ class OrderItem extends Equatable { orderId: json['orderId'] as String, orderType: OrderType.fromJson(json['orderType'] as String?), roleName: json['roleName'] as String, - date: DateTime.parse(json['date'] as String), - startsAt: DateTime.parse(json['startsAt'] as String), - endsAt: DateTime.parse(json['endsAt'] as String), + date: parseUtcToLocal(json['date'] as String), + startsAt: parseUtcToLocal(json['startsAt'] as String), + endsAt: parseUtcToLocal(json['endsAt'] as String), requiredWorkerCount: (json['requiredWorkerCount'] as num).toInt(), filledCount: (json['filledCount'] as num).toInt(), hourlyRateCents: (json['hourlyRateCents'] as num).toInt(), diff --git a/apps/mobile/packages/domain/lib/src/entities/orders/order_preview.dart b/apps/mobile/packages/domain/lib/src/entities/orders/order_preview.dart index 3bc41648..0ece8974 100644 --- a/apps/mobile/packages/domain/lib/src/entities/orders/order_preview.dart +++ b/apps/mobile/packages/domain/lib/src/entities/orders/order_preview.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// A preview of an order for reordering purposes. /// /// Returned by `GET /client/orders/:id/reorder-preview`. @@ -31,12 +33,8 @@ class OrderPreview extends Equatable { orderId: json['orderId'] as String, title: json['title'] as String, description: json['description'] as String?, - startsAt: json['startsAt'] != null - ? DateTime.parse(json['startsAt'] as String) - : null, - endsAt: json['endsAt'] != null - ? DateTime.parse(json['endsAt'] as String) - : null, + startsAt: tryParseUtcToLocal(json['startsAt'] as String?), + endsAt: tryParseUtcToLocal(json['endsAt'] as String?), locationName: json['locationName'] as String?, locationAddress: json['locationAddress'] as String?, metadata: json['metadata'] is Map @@ -128,8 +126,8 @@ class OrderPreviewShift extends Equatable { shiftId: json['shiftId'] as String, shiftCode: json['shiftCode'] as String, title: json['title'] as String, - startsAt: DateTime.parse(json['startsAt'] as String), - endsAt: DateTime.parse(json['endsAt'] as String), + startsAt: parseUtcToLocal(json['startsAt'] as String), + endsAt: parseUtcToLocal(json['endsAt'] as String), roles: rolesList, ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/orders/recent_order.dart b/apps/mobile/packages/domain/lib/src/entities/orders/recent_order.dart index 453a048b..f3096033 100644 --- a/apps/mobile/packages/domain/lib/src/entities/orders/recent_order.dart +++ b/apps/mobile/packages/domain/lib/src/entities/orders/recent_order.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart'; +import '../../core/utils/utc_parser.dart'; + /// A recently completed order available for reordering. /// /// Returned by `GET /client/reorders`. @@ -21,9 +23,7 @@ class RecentOrder extends Equatable { return RecentOrder( id: json['id'] as String, title: json['title'] as String, - date: json['date'] != null - ? DateTime.parse(json['date'] as String) - : null, + date: tryParseUtcToLocal(json['date'] as String?), hubName: json['hubName'] as String?, positionCount: (json['positionCount'] as num).toInt(), orderType: OrderType.fromJson(json['orderType'] as String?), diff --git a/apps/mobile/packages/domain/lib/src/entities/profile/certificate.dart b/apps/mobile/packages/domain/lib/src/entities/profile/certificate.dart index 388e154a..7a629fe5 100644 --- a/apps/mobile/packages/domain/lib/src/entities/profile/certificate.dart +++ b/apps/mobile/packages/domain/lib/src/entities/profile/certificate.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Status of a staff certificate. enum CertificateStatus { /// Certificate uploaded, pending verification. @@ -45,8 +47,8 @@ class StaffCertificate extends Equatable { fileUri: json['fileUri'] as String?, issuer: json['issuer'] as String?, certificateNumber: json['certificateNumber'] as String?, - issuedAt: json['issuedAt'] != null ? DateTime.parse(json['issuedAt'] as String) : null, - expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'] as String) : null, + issuedAt: tryParseUtcToLocal(json['issuedAt'] as String?), + expiresAt: tryParseUtcToLocal(json['expiresAt'] as String?), status: _parseStatus(json['status'] as String?), verificationStatus: json['verificationStatus'] as String?, ); diff --git a/apps/mobile/packages/domain/lib/src/entities/profile/profile_document.dart b/apps/mobile/packages/domain/lib/src/entities/profile/profile_document.dart index d044f4e5..97028fcf 100644 --- a/apps/mobile/packages/domain/lib/src/entities/profile/profile_document.dart +++ b/apps/mobile/packages/domain/lib/src/entities/profile/profile_document.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Status of a profile document. enum ProfileDocumentStatus { /// Document has not been uploaded yet. @@ -59,7 +61,7 @@ class ProfileDocument extends Equatable { staffDocumentId: json['staffDocumentId'] as String?, fileUri: json['fileUri'] as String?, status: _parseStatus(json['status'] as String?), - expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'] as String) : null, + expiresAt: tryParseUtcToLocal(json['expiresAt'] as String?), metadata: (json['metadata'] as Map?) ?? const {}, ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/ratings/staff_rating.dart b/apps/mobile/packages/domain/lib/src/entities/ratings/staff_rating.dart index a1a70391..a7977548 100644 --- a/apps/mobile/packages/domain/lib/src/entities/ratings/staff_rating.dart +++ b/apps/mobile/packages/domain/lib/src/entities/ratings/staff_rating.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// A review left for a staff member after an assignment. /// /// Maps to the V2 `staff_reviews` table. @@ -35,9 +37,7 @@ class StaffRating extends Equatable { rating: (json['rating'] as num).toInt(), reviewText: json['reviewText'] as String?, tags: tagsList, - createdAt: json['createdAt'] != null - ? DateTime.parse(json['createdAt'] as String) - : null, + createdAt: tryParseUtcToLocal(json['createdAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/reports/coverage_report.dart b/apps/mobile/packages/domain/lib/src/entities/reports/coverage_report.dart index 9ed90277..5930090d 100644 --- a/apps/mobile/packages/domain/lib/src/entities/reports/coverage_report.dart +++ b/apps/mobile/packages/domain/lib/src/entities/reports/coverage_report.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Coverage report with daily breakdown. /// /// Returned by `GET /client/reports/coverage`. @@ -75,7 +77,7 @@ class CoverageDayPoint extends Equatable { /// Deserialises a [CoverageDayPoint] from a V2 API JSON map. factory CoverageDayPoint.fromJson(Map json) { return CoverageDayPoint( - day: DateTime.parse(json['day'] as String), + day: parseUtcToLocal(json['day'] as String), needed: (json['needed'] as num).toInt(), filled: (json['filled'] as num).toInt(), coveragePercentage: (json['coveragePercentage'] as num).toDouble(), diff --git a/apps/mobile/packages/domain/lib/src/entities/reports/forecast_report.dart b/apps/mobile/packages/domain/lib/src/entities/reports/forecast_report.dart index 20c7a3a1..da7d7c1c 100644 --- a/apps/mobile/packages/domain/lib/src/entities/reports/forecast_report.dart +++ b/apps/mobile/packages/domain/lib/src/entities/reports/forecast_report.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Staffing and spend forecast report. /// /// Returned by `GET /client/reports/forecast`. @@ -83,7 +85,7 @@ class ForecastWeek extends Equatable { /// Deserialises a [ForecastWeek] from a V2 API JSON map. factory ForecastWeek.fromJson(Map json) { return ForecastWeek( - week: DateTime.parse(json['week'] as String), + week: parseUtcToLocal(json['week'] as String), shiftCount: (json['shiftCount'] as num).toInt(), workerHours: (json['workerHours'] as num).toDouble(), forecastSpendCents: (json['forecastSpendCents'] as num).toInt(), diff --git a/apps/mobile/packages/domain/lib/src/entities/reports/no_show_report.dart b/apps/mobile/packages/domain/lib/src/entities/reports/no_show_report.dart index f4f9047c..a9f13f6b 100644 --- a/apps/mobile/packages/domain/lib/src/entities/reports/no_show_report.dart +++ b/apps/mobile/packages/domain/lib/src/entities/reports/no_show_report.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// No-show report with per-worker incident details. /// /// Returned by `GET /client/reports/no-show`. @@ -143,7 +145,7 @@ class NoShowIncident extends Equatable { shiftId: json['shiftId'] as String, shiftTitle: json['shiftTitle'] as String, roleName: json['roleName'] as String, - date: DateTime.parse(json['date'] as String), + date: parseUtcToLocal(json['date'] as String), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/reports/spend_data_point.dart b/apps/mobile/packages/domain/lib/src/entities/reports/spend_data_point.dart index 30480fae..0e39e55f 100644 --- a/apps/mobile/packages/domain/lib/src/entities/reports/spend_data_point.dart +++ b/apps/mobile/packages/domain/lib/src/entities/reports/spend_data_point.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; import '../financial/spend_item.dart'; /// Spend report with total, chart data points, and category breakdown. @@ -71,7 +72,7 @@ class SpendDataPoint extends Equatable { /// Deserialises a [SpendDataPoint] from a V2 API JSON map. factory SpendDataPoint.fromJson(Map json) { return SpendDataPoint( - bucket: DateTime.parse(json['bucket'] as String), + bucket: parseUtcToLocal(json['bucket'] as String), amountCents: (json['amountCents'] as num).toInt(), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/assigned_shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/assigned_shift.dart index 11b27bc1..1ab5f69e 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/assigned_shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/assigned_shift.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; import 'package:krow_domain/src/entities/enums/assignment_status.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart'; @@ -33,9 +34,9 @@ class AssignedShift extends Equatable { shiftId: json['shiftId'] as String, roleName: json['roleName'] as String, location: json['location'] as String? ?? '', - date: DateTime.parse(json['date'] as String), - startTime: DateTime.parse(json['startTime'] as String), - endTime: DateTime.parse(json['endTime'] as String), + date: parseUtcToLocal(json['date'] as String), + startTime: parseUtcToLocal(json['startTime'] as String), + endTime: parseUtcToLocal(json['endTime'] as String), hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0, totalRateCents: json['totalRateCents'] as int? ?? 0, diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/cancelled_shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/cancelled_shift.dart index 6fb4741d..d2cff728 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/cancelled_shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/cancelled_shift.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; + /// A shift whose assignment was cancelled. /// /// Returned by `GET /staff/shifts/cancelled`. Shows past assignments @@ -22,7 +24,7 @@ class CancelledShift extends Equatable { shiftId: json['shiftId'] as String, title: json['title'] as String? ?? '', location: json['location'] as String? ?? '', - date: DateTime.parse(json['date'] as String), + date: parseUtcToLocal(json['date'] as String), cancellationReason: json['cancellationReason'] as String?, ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/completed_shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/completed_shift.dart index 54f29d7d..8f99fc47 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/completed_shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/completed_shift.dart @@ -1,5 +1,8 @@ import 'package:equatable/equatable.dart'; -import 'package:krow_domain/krow_domain.dart'; + +import 'package:krow_domain/src/core/utils/utc_parser.dart'; +import 'package:krow_domain/src/entities/enums/assignment_status.dart'; +import 'package:krow_domain/src/entities/enums/payment_status.dart'; /// A shift the staff member has completed. /// @@ -34,12 +37,12 @@ class CompletedShift extends Equatable { title: json['title'] as String? ?? '', location: json['location'] as String? ?? '', clientName: json['clientName'] as String? ?? '', - date: DateTime.parse(json['date'] as String), + date: parseUtcToLocal(json['date'] as String), startTime: json['startTime'] != null - ? DateTime.parse(json['startTime'] as String) + ? parseUtcToLocal(json['startTime'] as String) : DateTime.now(), endTime: json['endTime'] != null - ? DateTime.parse(json['endTime'] as String) + ? parseUtcToLocal(json['endTime'] as String) : DateTime.now(), minutesWorked: json['minutesWorked'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/open_shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/open_shift.dart index 856deef5..f2b5c9a6 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/open_shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/open_shift.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart'; /// An open shift available for the staff member to apply to. @@ -32,9 +33,9 @@ class OpenShift extends Equatable { roleName: json['roleName'] as String, clientName: json['clientName'] as String? ?? '', location: json['location'] as String? ?? '', - date: DateTime.parse(json['date'] as String), - startTime: DateTime.parse(json['startTime'] as String), - endTime: DateTime.parse(json['endTime'] as String), + date: parseUtcToLocal(json['date'] as String), + startTime: parseUtcToLocal(json['startTime'] as String), + endTime: parseUtcToLocal(json['endTime'] as String), hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0, orderType: OrderType.fromJson(json['orderType'] as String?), diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/pending_assignment.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/pending_assignment.dart index b22d6bc4..c96c5810 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/pending_assignment.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/pending_assignment.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; + /// An assignment awaiting the staff member's acceptance. /// /// Returned by `GET /staff/shifts/pending`. These are assignments with @@ -24,10 +26,10 @@ class PendingAssignment extends Equatable { shiftId: json['shiftId'] as String, title: json['title'] as String? ?? '', roleName: json['roleName'] as String, - startTime: DateTime.parse(json['startTime'] as String), - endTime: DateTime.parse(json['endTime'] as String), + startTime: parseUtcToLocal(json['startTime'] as String), + endTime: parseUtcToLocal(json['endTime'] as String), location: json['location'] as String? ?? '', - responseDeadline: DateTime.parse(json['responseDeadline'] as String), + responseDeadline: parseUtcToLocal(json['responseDeadline'] as String), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart index 8b45cf75..69900461 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/shift.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; import 'package:krow_domain/src/entities/enums/shift_status.dart'; /// Core shift entity aligned with the V2 `shifts` table. @@ -48,10 +49,10 @@ class Shift extends Equatable { clientName ?? '', status: ShiftStatus.fromJson(json['status'] as String?), - startsAt: DateTime.parse( + startsAt: parseUtcToLocal( json['startsAt'] as String? ?? json['startTime'] as String, ), - endsAt: DateTime.parse( + endsAt: parseUtcToLocal( json['endsAt'] as String? ?? json['endTime'] as String, ), timezone: json['timezone'] as String? ?? 'UTC', diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/shift_detail.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/shift_detail.dart index c4082982..38e2dc23 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/shift_detail.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/shift_detail.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; import 'package:krow_domain/src/entities/enums/application_status.dart'; import 'package:krow_domain/src/entities/enums/assignment_status.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart'; @@ -53,9 +54,9 @@ class ShiftDetail extends Equatable { clientName: json['clientName'] as String? ?? '', latitude: Shift.parseDouble(json['latitude']), longitude: Shift.parseDouble(json['longitude']), - date: DateTime.parse(json['date'] as String), - startTime: DateTime.parse(json['startTime'] as String), - endTime: DateTime.parse(json['endTime'] as String), + date: parseUtcToLocal(json['date'] as String), + startTime: parseUtcToLocal(json['startTime'] as String), + endTime: parseUtcToLocal(json['endTime'] as String), roleId: json['roleId'] as String, roleName: json['roleName'] as String, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, diff --git a/apps/mobile/packages/domain/lib/src/entities/shifts/today_shift.dart b/apps/mobile/packages/domain/lib/src/entities/shifts/today_shift.dart index 01248ff3..cefd2f26 100644 --- a/apps/mobile/packages/domain/lib/src/entities/shifts/today_shift.dart +++ b/apps/mobile/packages/domain/lib/src/entities/shifts/today_shift.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; +import 'package:krow_domain/src/core/utils/utc_parser.dart'; import 'package:krow_domain/src/entities/enums/attendance_status_type.dart'; /// A shift assigned to the staff member for today. @@ -33,8 +34,8 @@ class TodayShift extends Equatable { shiftId: json['shiftId'] as String, roleName: json['roleName'] as String, location: json['location'] as String? ?? '', - startTime: DateTime.parse(json['startTime'] as String), - endTime: DateTime.parse(json['endTime'] as String), + startTime: parseUtcToLocal(json['startTime'] as String), + endTime: parseUtcToLocal(json['endTime'] as String), attendanceStatus: AttendanceStatusType.fromJson(json['attendanceStatus'] as String?), clientName: json['clientName'] as String? ?? '', hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, @@ -42,9 +43,7 @@ class TodayShift extends Equatable { totalRateCents: json['totalRateCents'] as int? ?? 0, totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0, locationAddress: json['locationAddress'] as String?, - clockInAt: json['clockInAt'] != null - ? DateTime.parse(json['clockInAt'] as String) - : null, + clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/users/biz_member.dart b/apps/mobile/packages/domain/lib/src/entities/users/biz_member.dart index ee071a75..fce809bc 100644 --- a/apps/mobile/packages/domain/lib/src/entities/users/biz_member.dart +++ b/apps/mobile/packages/domain/lib/src/entities/users/biz_member.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Membership status within a business. enum BusinessMembershipStatus { /// The user has been invited but has not accepted. @@ -63,8 +65,8 @@ class BusinessMembership extends Equatable { businessName: json['businessName'] as String?, businessSlug: json['businessSlug'] as String?, metadata: (json['metadata'] as Map?) ?? const {}, - createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, - updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + createdAt: tryParseUtcToLocal(json['createdAt'] as String?), + updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/users/staff.dart b/apps/mobile/packages/domain/lib/src/entities/users/staff.dart index d46849a0..bf2c9d89 100644 --- a/apps/mobile/packages/domain/lib/src/entities/users/staff.dart +++ b/apps/mobile/packages/domain/lib/src/entities/users/staff.dart @@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:krow_domain/krow_domain.dart' show OnboardingStatus, StaffStatus; +import '../../core/utils/utc_parser.dart'; + /// Represents a worker profile in the KROW platform. /// /// Maps to the V2 `staffs` table. Linked to a [User] via [userId]. @@ -47,8 +49,8 @@ class Staff extends Equatable { workforceId: json['workforceId'] as String?, vendorId: json['vendorId'] as String?, workforceNumber: json['workforceNumber'] as String?, - createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, - updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + createdAt: tryParseUtcToLocal(json['createdAt'] as String?), + updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?), ); } diff --git a/apps/mobile/packages/domain/lib/src/entities/users/user.dart b/apps/mobile/packages/domain/lib/src/entities/users/user.dart index fe2c5785..13eacc21 100644 --- a/apps/mobile/packages/domain/lib/src/entities/users/user.dart +++ b/apps/mobile/packages/domain/lib/src/entities/users/user.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../core/utils/utc_parser.dart'; + /// Account status for a platform user. enum UserStatus { /// User is active and can sign in. @@ -37,8 +39,8 @@ class User extends Equatable { phone: json['phone'] as String?, status: _parseUserStatus(json['status'] as String?), metadata: (json['metadata'] as Map?) ?? const {}, - createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, - updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, + createdAt: tryParseUtcToLocal(json['createdAt'] as String?), + updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?), ); } diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart index c3cf54d2..eace8942 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart @@ -75,7 +75,7 @@ class ReorderWidget extends StatelessWidget { borderRadius: UiConstants.radiusLg, ), child: const Icon( - UiIcons.building, + UiIcons.briefcase, size: 16, color: UiColors.primary, ), @@ -104,18 +104,6 @@ class ReorderWidget extends StatelessWidget { ], ), ), - // Column( - // crossAxisAlignment: CrossAxisAlignment.end, - // children: [ - // // ASSUMPTION: No i18n key for 'positions' under - // // reorder section — carrying forward existing - // // hardcoded string pattern for this migration. - // Text( - // '${order.positionCount} positions', - // style: UiTypography.footnote2r.textSecondary, - // ), - // ], - // ), ], ), const SizedBox(height: UiConstants.space3), @@ -130,7 +118,7 @@ class ReorderWidget extends StatelessWidget { ), const SizedBox(width: UiConstants.space2), _Badge( - icon: UiIcons.building, + icon: UiIcons.users, text: '${order.positionCount}', color: UiColors.textSecondary, bg: UiColors.buttonSecondaryStill, diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/order_edit_sheet.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/order_edit_sheet.dart index 44add689..7ba84dc8 100644 --- a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/order_edit_sheet.dart +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/order_edit_sheet.dart @@ -48,13 +48,13 @@ class OrderEditSheetState extends State { _orderNameController = TextEditingController(text: widget.order.roleName); final String startHH = - widget.order.startsAt.toLocal().hour.toString().padLeft(2, '0'); + widget.order.startsAt.hour.toString().padLeft(2, '0'); final String startMM = - widget.order.startsAt.toLocal().minute.toString().padLeft(2, '0'); + widget.order.startsAt.minute.toString().padLeft(2, '0'); final String endHH = - widget.order.endsAt.toLocal().hour.toString().padLeft(2, '0'); + widget.order.endsAt.hour.toString().padLeft(2, '0'); final String endMM = - widget.order.endsAt.toLocal().minute.toString().padLeft(2, '0'); + widget.order.endsAt.minute.toString().padLeft(2, '0'); _positions = >[ { diff --git a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_order_card.dart b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_order_card.dart index fa9fdd1a..969aed43 100644 --- a/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_order_card.dart +++ b/apps/mobile/packages/features/client/orders/view_orders/lib/src/presentation/widgets/view_order_card.dart @@ -77,9 +77,8 @@ class _ViewOrderCardState extends State { /// Formats a [DateTime] to a display time string (e.g. "9:00 AM"). String _formatTime({required DateTime dateTime}) { - final DateTime local = dateTime.toLocal(); - final int hour24 = local.hour; - final int minute = local.minute; + final int hour24 = dateTime.hour; + final int minute = dateTime.minute; final String ampm = hour24 >= 12 ? 'PM' : 'AM'; int hour = hour24 % 12; if (hour == 0) hour = 12;