feat: add UTC parsing utilities and update date handling across entities

- Introduced `utc_parser.dart` with functions to convert UTC timestamps to local time.
- Updated date parsing in various entities to use the new utility functions for consistency.
- Refactored date handling in `BenefitHistory`, `Business`, `AttendanceStatus`, `AssignedWorker`, `TimeRange`, `Invoice`, `PaymentChartPoint`, `StaffPayment`, `TimeCardEntry`, `OrderItem`, `OrderPreview`, `RecentOrder`, `StaffRating`, `CoverageDayPoint`, `ForecastWeek`, `NoShowIncident`, `SpendDataPoint`, `AssignedShift`, `CancelledShift`, `CompletedShift`, `OpenShift`, `PendingAssignment`, `Shift`, `ShiftDetail`, `TodayShift`, `BusinessMembership`, and `Staff`.
- Updated `ReorderWidget` and `OrderEditSheet` to handle date formatting correctly.
This commit is contained in:
Achintha Isuru
2026-03-19 13:00:17 -04:00
parent eacf34999b
commit 5792aa6e98
37 changed files with 136 additions and 109 deletions

View File

@@ -32,7 +32,7 @@ class AuthInterceptor extends Interceptor {
if (!skipAuth) { if (!skipAuth) {
final User? user = FirebaseAuth.instance.currentUser; final User? user = FirebaseAuth.instance.currentUser;
if (user != null) { if (user != null) {
final String? token = await user.getIdToken(); final String? token = await user.getIdToken();
if (token != null) { if (token != null) {
options.headers['Authorization'] = 'Bearer $token'; options.headers['Authorization'] = 'Bearer $token';
} }

View File

@@ -4,4 +4,9 @@ class DateTimeUtils {
static DateTime toDeviceTime(DateTime date) { static DateTime toDeviceTime(DateTime date) {
return date.toLocal(); return date.toLocal();
} }
/// Converts a local [DateTime] back to UTC for API payloads.
static String toUtcIso(DateTime local) {
return local.toUtc().toIso8601String();
}
} }

View File

@@ -25,6 +25,9 @@ export 'src/entities/enums/staff_skill.dart';
export 'src/entities/enums/staff_status.dart'; export 'src/entities/enums/staff_status.dart';
export 'src/entities/enums/user_role.dart'; export 'src/entities/enums/user_role.dart';
// Utils
export 'src/core/utils/utc_parser.dart';
// Core // Core
export 'src/core/services/api_services/api_endpoint.dart'; export 'src/core/services/api_services/api_endpoint.dart';
export 'src/core/services/api_services/api_response.dart'; export 'src/core/services/api_services/api_response.dart';

View File

@@ -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;

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/benefit_status.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. /// A historical record of a staff benefit accrual period.
/// ///
/// Returned by `GET /staff/profile/benefits/history`. /// Returned by `GET /staff/profile/benefits/history`.
@@ -28,10 +30,8 @@ class BenefitHistory extends Equatable {
benefitType: json['benefitType'] as String, benefitType: json['benefitType'] as String,
title: json['title'] as String, title: json['title'] as String,
status: BenefitStatus.fromJson(json['status'] as String?), status: BenefitStatus.fromJson(json['status'] as String?),
effectiveAt: DateTime.parse(json['effectiveAt'] as String), effectiveAt: parseUtcToLocal(json['effectiveAt'] as String),
endedAt: json['endedAt'] != null endedAt: tryParseUtcToLocal(json['endedAt'] as String?),
? DateTime.parse(json['endedAt'] as String)
: null,
trackedHours: (json['trackedHours'] as num).toInt(), trackedHours: (json['trackedHours'] as num).toInt(),
targetHours: (json['targetHours'] as num).toInt(), targetHours: (json['targetHours'] as num).toInt(),
notes: json['notes'] as String?, notes: json['notes'] as String?,

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/business_status.dart'; import 'package:krow_domain/src/entities/enums/business_status.dart';
import '../../core/utils/utc_parser.dart';
/// A client company registered on the platform. /// A client company registered on the platform.
/// ///
/// Maps to the V2 `businesses` table. /// Maps to the V2 `businesses` table.
@@ -35,12 +37,8 @@ class Business extends Equatable {
metadata: json['metadata'] is Map metadata: json['metadata'] is Map
? Map<String, dynamic>.from(json['metadata'] as Map<dynamic, dynamic>) ? Map<String, dynamic>.from(json['metadata'] as Map<dynamic, dynamic>)
: const <String, dynamic>{}, : const <String, dynamic>{},
createdAt: json['createdAt'] != null createdAt: tryParseUtcToLocal(json['createdAt'] as String?),
? DateTime.parse(json['createdAt'] as String) updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?),
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/attendance_status_type.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. /// Current clock-in / attendance status of the staff member.
/// ///
/// Returned by `GET /staff/clock-in/status`. When no open session exists /// 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?, activeShiftId: json['activeShiftId'] as String?,
attendanceStatus: attendanceStatus:
AttendanceStatusType.fromJson(json['attendanceStatus'] as String?), AttendanceStatusType.fromJson(json['attendanceStatus'] as String?),
clockInAt: json['clockInAt'] != null clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?),
? DateTime.parse(json['clockInAt'] as String)
: null,
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/assignment_status.dart'; import 'package:krow_domain/src/entities/enums/assignment_status.dart';
import '../../core/utils/utc_parser.dart';
/// A worker assigned to a coverage shift. /// A worker assigned to a coverage shift.
/// ///
/// Nested within [ShiftWithWorkers]. /// Nested within [ShiftWithWorkers].
@@ -23,9 +25,7 @@ class AssignedWorker extends Equatable {
staffId: json['staffId'] as String, staffId: json['staffId'] as String,
fullName: json['fullName'] as String, fullName: json['fullName'] as String,
status: AssignmentStatus.fromJson(json['status'] as String?), status: AssignmentStatus.fromJson(json['status'] as String?),
checkInAt: json['checkInAt'] != null checkInAt: tryParseUtcToLocal(json['checkInAt'] as String?),
? DateTime.parse(json['checkInAt'] as String)
: null,
hasReview: json['hasReview'] as bool? ?? false, hasReview: json['hasReview'] as bool? ?? false,
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// A time range with start and end timestamps. /// A time range with start and end timestamps.
/// ///
/// Used within [ShiftWithWorkers] for shift time windows. /// Used within [ShiftWithWorkers] for shift time windows.
@@ -13,8 +15,8 @@ class TimeRange extends Equatable {
/// Deserialises a [TimeRange] from a V2 API JSON map. /// Deserialises a [TimeRange] from a V2 API JSON map.
factory TimeRange.fromJson(Map<String, dynamic> json) { factory TimeRange.fromJson(Map<String, dynamic> json) {
return TimeRange( return TimeRange(
startsAt: DateTime.parse(json['startsAt'] as String), startsAt: parseUtcToLocal(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String), endsAt: parseUtcToLocal(json['endsAt'] as String),
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/invoice_status.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. /// An invoice issued to a business for services rendered.
/// ///
/// Returned by `GET /client/billing/invoices/*`. /// Returned by `GET /client/billing/invoices/*`.
@@ -25,12 +27,8 @@ class Invoice extends Equatable {
invoiceNumber: json['invoiceNumber'] as String, invoiceNumber: json['invoiceNumber'] as String,
amountCents: (json['amountCents'] as num).toInt(), amountCents: (json['amountCents'] as num).toInt(),
status: InvoiceStatus.fromJson(json['status'] as String?), status: InvoiceStatus.fromJson(json['status'] as String?),
dueDate: json['dueDate'] != null dueDate: tryParseUtcToLocal(json['dueDate'] as String?),
? DateTime.parse(json['dueDate'] as String) paymentDate: tryParseUtcToLocal(json['paymentDate'] as String?),
: null,
paymentDate: json['paymentDate'] != null
? DateTime.parse(json['paymentDate'] as String)
: null,
vendorId: json['vendorId'] as String?, vendorId: json['vendorId'] as String?,
vendorName: json['vendorName'] as String?, vendorName: json['vendorName'] as String?,
); );

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// A single data point in the staff payment chart. /// A single data point in the staff payment chart.
/// ///
/// Returned by `GET /staff/payments/chart`. /// Returned by `GET /staff/payments/chart`.
@@ -13,7 +15,7 @@ class PaymentChartPoint extends Equatable {
/// Deserialises a [PaymentChartPoint] from a V2 API JSON map. /// Deserialises a [PaymentChartPoint] from a V2 API JSON map.
factory PaymentChartPoint.fromJson(Map<String, dynamic> json) { factory PaymentChartPoint.fromJson(Map<String, dynamic> json) {
return PaymentChartPoint( return PaymentChartPoint(
bucket: DateTime.parse(json['bucket'] as String), bucket: parseUtcToLocal(json['bucket'] as String),
amountCents: (json['amountCents'] as num).toInt(), amountCents: (json['amountCents'] as num).toInt(),
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/payment_status.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. /// A single payment record for a staff member.
/// ///
/// Returned by `GET /staff/payments/history`. /// Returned by `GET /staff/payments/history`.
@@ -23,7 +25,7 @@ class PaymentRecord extends Equatable {
return PaymentRecord( return PaymentRecord(
paymentId: json['paymentId'] as String, paymentId: json['paymentId'] as String,
amountCents: (json['amountCents'] as num).toInt(), 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?), status: PaymentStatus.fromJson(json['status'] as String?),
shiftName: json['shiftName'] as String?, shiftName: json['shiftName'] as String?,
location: json['location'] as String?, location: json['location'] as String?,

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// A single time-card entry for a completed shift. /// A single time-card entry for a completed shift.
/// ///
/// Returned by `GET /staff/profile/time-card`. /// Returned by `GET /staff/profile/time-card`.
@@ -19,15 +21,11 @@ class TimeCardEntry extends Equatable {
/// Deserialises a [TimeCardEntry] from a V2 API JSON map. /// Deserialises a [TimeCardEntry] from a V2 API JSON map.
factory TimeCardEntry.fromJson(Map<String, dynamic> json) { factory TimeCardEntry.fromJson(Map<String, dynamic> json) {
return TimeCardEntry( return TimeCardEntry(
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
shiftName: json['shiftName'] as String, shiftName: json['shiftName'] as String,
location: json['location'] as String?, location: json['location'] as String?,
clockInAt: json['clockInAt'] != null clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?),
? DateTime.parse(json['clockInAt'] as String) clockOutAt: tryParseUtcToLocal(json['clockOutAt'] as String?),
: null,
clockOutAt: json['clockOutAt'] != null
? DateTime.parse(json['clockOutAt'] as String)
: null,
minutesWorked: (json['minutesWorked'] as num).toInt(), minutesWorked: (json['minutesWorked'] as num).toInt(),
hourlyRateCents: json['hourlyRateCents'] != null hourlyRateCents: json['hourlyRateCents'] != null
? (json['hourlyRateCents'] as num).toInt() ? (json['hourlyRateCents'] as num).toInt()

View File

@@ -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/order_type.dart';
import 'package:krow_domain/src/entities/enums/shift_status.dart'; import 'package:krow_domain/src/entities/enums/shift_status.dart';
import '../../core/utils/utc_parser.dart';
import 'assigned_worker_summary.dart'; import 'assigned_worker_summary.dart';
/// A line item within an order, representing a role needed for a shift. /// 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, orderId: json['orderId'] as String,
orderType: OrderType.fromJson(json['orderType'] as String?), orderType: OrderType.fromJson(json['orderType'] as String?),
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
startsAt: DateTime.parse(json['startsAt'] as String), startsAt: parseUtcToLocal(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String), endsAt: parseUtcToLocal(json['endsAt'] as String),
requiredWorkerCount: (json['requiredWorkerCount'] as num).toInt(), requiredWorkerCount: (json['requiredWorkerCount'] as num).toInt(),
filledCount: (json['filledCount'] as num).toInt(), filledCount: (json['filledCount'] as num).toInt(),
hourlyRateCents: (json['hourlyRateCents'] as num).toInt(), hourlyRateCents: (json['hourlyRateCents'] as num).toInt(),

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// A preview of an order for reordering purposes. /// A preview of an order for reordering purposes.
/// ///
/// Returned by `GET /client/orders/:id/reorder-preview`. /// Returned by `GET /client/orders/:id/reorder-preview`.
@@ -31,12 +33,8 @@ class OrderPreview extends Equatable {
orderId: json['orderId'] as String, orderId: json['orderId'] as String,
title: json['title'] as String, title: json['title'] as String,
description: json['description'] as String?, description: json['description'] as String?,
startsAt: json['startsAt'] != null startsAt: tryParseUtcToLocal(json['startsAt'] as String?),
? DateTime.parse(json['startsAt'] as String) endsAt: tryParseUtcToLocal(json['endsAt'] as String?),
: null,
endsAt: json['endsAt'] != null
? DateTime.parse(json['endsAt'] as String)
: null,
locationName: json['locationName'] as String?, locationName: json['locationName'] as String?,
locationAddress: json['locationAddress'] as String?, locationAddress: json['locationAddress'] as String?,
metadata: json['metadata'] is Map metadata: json['metadata'] is Map
@@ -128,8 +126,8 @@ class OrderPreviewShift extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
shiftCode: json['shiftCode'] as String, shiftCode: json['shiftCode'] as String,
title: json['title'] as String, title: json['title'] as String,
startsAt: DateTime.parse(json['startsAt'] as String), startsAt: parseUtcToLocal(json['startsAt'] as String),
endsAt: DateTime.parse(json['endsAt'] as String), endsAt: parseUtcToLocal(json['endsAt'] as String),
roles: rolesList, roles: rolesList,
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/enums/order_type.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart';
import '../../core/utils/utc_parser.dart';
/// A recently completed order available for reordering. /// A recently completed order available for reordering.
/// ///
/// Returned by `GET /client/reorders`. /// Returned by `GET /client/reorders`.
@@ -21,9 +23,7 @@ class RecentOrder extends Equatable {
return RecentOrder( return RecentOrder(
id: json['id'] as String, id: json['id'] as String,
title: json['title'] as String, title: json['title'] as String,
date: json['date'] != null date: tryParseUtcToLocal(json['date'] as String?),
? DateTime.parse(json['date'] as String)
: null,
hubName: json['hubName'] as String?, hubName: json['hubName'] as String?,
positionCount: (json['positionCount'] as num).toInt(), positionCount: (json['positionCount'] as num).toInt(),
orderType: OrderType.fromJson(json['orderType'] as String?), orderType: OrderType.fromJson(json['orderType'] as String?),

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Status of a staff certificate. /// Status of a staff certificate.
enum CertificateStatus { enum CertificateStatus {
/// Certificate uploaded, pending verification. /// Certificate uploaded, pending verification.
@@ -45,8 +47,8 @@ class StaffCertificate extends Equatable {
fileUri: json['fileUri'] as String?, fileUri: json['fileUri'] as String?,
issuer: json['issuer'] as String?, issuer: json['issuer'] as String?,
certificateNumber: json['certificateNumber'] as String?, certificateNumber: json['certificateNumber'] as String?,
issuedAt: json['issuedAt'] != null ? DateTime.parse(json['issuedAt'] as String) : null, issuedAt: tryParseUtcToLocal(json['issuedAt'] as String?),
expiresAt: json['expiresAt'] != null ? DateTime.parse(json['expiresAt'] as String) : null, expiresAt: tryParseUtcToLocal(json['expiresAt'] as String?),
status: _parseStatus(json['status'] as String?), status: _parseStatus(json['status'] as String?),
verificationStatus: json['verificationStatus'] as String?, verificationStatus: json['verificationStatus'] as String?,
); );

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Status of a profile document. /// Status of a profile document.
enum ProfileDocumentStatus { enum ProfileDocumentStatus {
/// Document has not been uploaded yet. /// Document has not been uploaded yet.
@@ -59,7 +61,7 @@ class ProfileDocument extends Equatable {
staffDocumentId: json['staffDocumentId'] as String?, staffDocumentId: json['staffDocumentId'] as String?,
fileUri: json['fileUri'] as String?, fileUri: json['fileUri'] as String?,
status: _parseStatus(json['status'] 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<String, dynamic>?) ?? const <String, dynamic>{}, metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// A review left for a staff member after an assignment. /// A review left for a staff member after an assignment.
/// ///
/// Maps to the V2 `staff_reviews` table. /// Maps to the V2 `staff_reviews` table.
@@ -35,9 +37,7 @@ class StaffRating extends Equatable {
rating: (json['rating'] as num).toInt(), rating: (json['rating'] as num).toInt(),
reviewText: json['reviewText'] as String?, reviewText: json['reviewText'] as String?,
tags: tagsList, tags: tagsList,
createdAt: json['createdAt'] != null createdAt: tryParseUtcToLocal(json['createdAt'] as String?),
? DateTime.parse(json['createdAt'] as String)
: null,
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Coverage report with daily breakdown. /// Coverage report with daily breakdown.
/// ///
/// Returned by `GET /client/reports/coverage`. /// Returned by `GET /client/reports/coverage`.
@@ -75,7 +77,7 @@ class CoverageDayPoint extends Equatable {
/// Deserialises a [CoverageDayPoint] from a V2 API JSON map. /// Deserialises a [CoverageDayPoint] from a V2 API JSON map.
factory CoverageDayPoint.fromJson(Map<String, dynamic> json) { factory CoverageDayPoint.fromJson(Map<String, dynamic> json) {
return CoverageDayPoint( return CoverageDayPoint(
day: DateTime.parse(json['day'] as String), day: parseUtcToLocal(json['day'] as String),
needed: (json['needed'] as num).toInt(), needed: (json['needed'] as num).toInt(),
filled: (json['filled'] as num).toInt(), filled: (json['filled'] as num).toInt(),
coveragePercentage: (json['coveragePercentage'] as num).toDouble(), coveragePercentage: (json['coveragePercentage'] as num).toDouble(),

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Staffing and spend forecast report. /// Staffing and spend forecast report.
/// ///
/// Returned by `GET /client/reports/forecast`. /// Returned by `GET /client/reports/forecast`.
@@ -83,7 +85,7 @@ class ForecastWeek extends Equatable {
/// Deserialises a [ForecastWeek] from a V2 API JSON map. /// Deserialises a [ForecastWeek] from a V2 API JSON map.
factory ForecastWeek.fromJson(Map<String, dynamic> json) { factory ForecastWeek.fromJson(Map<String, dynamic> json) {
return ForecastWeek( return ForecastWeek(
week: DateTime.parse(json['week'] as String), week: parseUtcToLocal(json['week'] as String),
shiftCount: (json['shiftCount'] as num).toInt(), shiftCount: (json['shiftCount'] as num).toInt(),
workerHours: (json['workerHours'] as num).toDouble(), workerHours: (json['workerHours'] as num).toDouble(),
forecastSpendCents: (json['forecastSpendCents'] as num).toInt(), forecastSpendCents: (json['forecastSpendCents'] as num).toInt(),

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// No-show report with per-worker incident details. /// No-show report with per-worker incident details.
/// ///
/// Returned by `GET /client/reports/no-show`. /// Returned by `GET /client/reports/no-show`.
@@ -143,7 +145,7 @@ class NoShowIncident extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
shiftTitle: json['shiftTitle'] as String, shiftTitle: json['shiftTitle'] as String,
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
); );
} }

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
import '../financial/spend_item.dart'; import '../financial/spend_item.dart';
/// Spend report with total, chart data points, and category breakdown. /// 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. /// Deserialises a [SpendDataPoint] from a V2 API JSON map.
factory SpendDataPoint.fromJson(Map<String, dynamic> json) { factory SpendDataPoint.fromJson(Map<String, dynamic> json) {
return SpendDataPoint( return SpendDataPoint(
bucket: DateTime.parse(json['bucket'] as String), bucket: parseUtcToLocal(json['bucket'] as String),
amountCents: (json['amountCents'] as num).toInt(), amountCents: (json['amountCents'] as num).toInt(),
); );
} }

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/assignment_status.dart';
import 'package:krow_domain/src/entities/enums/order_type.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart';
@@ -33,9 +34,9 @@ class AssignedShift extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
location: json['location'] as String? ?? '', location: json['location'] as String? ?? '',
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
startTime: DateTime.parse(json['startTime'] as String), startTime: parseUtcToLocal(json['startTime'] as String),
endTime: DateTime.parse(json['endTime'] as String), endTime: parseUtcToLocal(json['endTime'] as String),
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0, hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
totalRateCents: json['totalRateCents'] as int? ?? 0, totalRateCents: json['totalRateCents'] as int? ?? 0,

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/core/utils/utc_parser.dart';
/// A shift whose assignment was cancelled. /// A shift whose assignment was cancelled.
/// ///
/// Returned by `GET /staff/shifts/cancelled`. Shows past assignments /// Returned by `GET /staff/shifts/cancelled`. Shows past assignments
@@ -22,7 +24,7 @@ class CancelledShift extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
title: json['title'] as String? ?? '', title: json['title'] as String? ?? '',
location: json['location'] 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?, cancellationReason: json['cancellationReason'] as String?,
); );
} }

View File

@@ -1,5 +1,8 @@
import 'package:equatable/equatable.dart'; 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. /// A shift the staff member has completed.
/// ///
@@ -34,12 +37,12 @@ class CompletedShift extends Equatable {
title: json['title'] as String? ?? '', title: json['title'] as String? ?? '',
location: json['location'] as String? ?? '', location: json['location'] as String? ?? '',
clientName: json['clientName'] as String? ?? '', clientName: json['clientName'] as String? ?? '',
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
startTime: json['startTime'] != null startTime: json['startTime'] != null
? DateTime.parse(json['startTime'] as String) ? parseUtcToLocal(json['startTime'] as String)
: DateTime.now(), : DateTime.now(),
endTime: json['endTime'] != null endTime: json['endTime'] != null
? DateTime.parse(json['endTime'] as String) ? parseUtcToLocal(json['endTime'] as String)
: DateTime.now(), : DateTime.now(),
minutesWorked: json['minutesWorked'] as int? ?? 0, minutesWorked: json['minutesWorked'] as int? ?? 0,
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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'; import 'package:krow_domain/src/entities/enums/order_type.dart';
/// An open shift available for the staff member to apply to. /// An open shift available for the staff member to apply to.
@@ -32,9 +33,9 @@ class OpenShift extends Equatable {
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
clientName: json['clientName'] as String? ?? '', clientName: json['clientName'] as String? ?? '',
location: json['location'] as String? ?? '', location: json['location'] as String? ?? '',
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
startTime: DateTime.parse(json['startTime'] as String), startTime: parseUtcToLocal(json['startTime'] as String),
endTime: DateTime.parse(json['endTime'] as String), endTime: parseUtcToLocal(json['endTime'] as String),
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0, hourlyRate: (json['hourlyRate'] as num?)?.toDouble() ?? 0.0,
orderType: OrderType.fromJson(json['orderType'] as String?), orderType: OrderType.fromJson(json['orderType'] as String?),

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/core/utils/utc_parser.dart';
/// An assignment awaiting the staff member's acceptance. /// An assignment awaiting the staff member's acceptance.
/// ///
/// Returned by `GET /staff/shifts/pending`. These are assignments with /// Returned by `GET /staff/shifts/pending`. These are assignments with
@@ -24,10 +26,10 @@ class PendingAssignment extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
title: json['title'] as String? ?? '', title: json['title'] as String? ?? '',
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
startTime: DateTime.parse(json['startTime'] as String), startTime: parseUtcToLocal(json['startTime'] as String),
endTime: DateTime.parse(json['endTime'] as String), endTime: parseUtcToLocal(json['endTime'] as String),
location: json['location'] as String? ?? '', location: json['location'] as String? ?? '',
responseDeadline: DateTime.parse(json['responseDeadline'] as String), responseDeadline: parseUtcToLocal(json['responseDeadline'] as String),
); );
} }

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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'; import 'package:krow_domain/src/entities/enums/shift_status.dart';
/// Core shift entity aligned with the V2 `shifts` table. /// Core shift entity aligned with the V2 `shifts` table.
@@ -48,10 +49,10 @@ class Shift extends Equatable {
clientName ?? clientName ??
'', '',
status: ShiftStatus.fromJson(json['status'] as String?), status: ShiftStatus.fromJson(json['status'] as String?),
startsAt: DateTime.parse( startsAt: parseUtcToLocal(
json['startsAt'] as String? ?? json['startTime'] as String, json['startsAt'] as String? ?? json['startTime'] as String,
), ),
endsAt: DateTime.parse( endsAt: parseUtcToLocal(
json['endsAt'] as String? ?? json['endTime'] as String, json['endsAt'] as String? ?? json['endTime'] as String,
), ),
timezone: json['timezone'] as String? ?? 'UTC', timezone: json['timezone'] as String? ?? 'UTC',

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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/application_status.dart';
import 'package:krow_domain/src/entities/enums/assignment_status.dart'; import 'package:krow_domain/src/entities/enums/assignment_status.dart';
import 'package:krow_domain/src/entities/enums/order_type.dart'; import 'package:krow_domain/src/entities/enums/order_type.dart';
@@ -53,9 +54,9 @@ class ShiftDetail extends Equatable {
clientName: json['clientName'] as String? ?? '', clientName: json['clientName'] as String? ?? '',
latitude: Shift.parseDouble(json['latitude']), latitude: Shift.parseDouble(json['latitude']),
longitude: Shift.parseDouble(json['longitude']), longitude: Shift.parseDouble(json['longitude']),
date: DateTime.parse(json['date'] as String), date: parseUtcToLocal(json['date'] as String),
startTime: DateTime.parse(json['startTime'] as String), startTime: parseUtcToLocal(json['startTime'] as String),
endTime: DateTime.parse(json['endTime'] as String), endTime: parseUtcToLocal(json['endTime'] as String),
roleId: json['roleId'] as String, roleId: json['roleId'] as String,
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart'; 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'; import 'package:krow_domain/src/entities/enums/attendance_status_type.dart';
/// A shift assigned to the staff member for today. /// A shift assigned to the staff member for today.
@@ -33,8 +34,8 @@ class TodayShift extends Equatable {
shiftId: json['shiftId'] as String, shiftId: json['shiftId'] as String,
roleName: json['roleName'] as String, roleName: json['roleName'] as String,
location: json['location'] as String? ?? '', location: json['location'] as String? ?? '',
startTime: DateTime.parse(json['startTime'] as String), startTime: parseUtcToLocal(json['startTime'] as String),
endTime: DateTime.parse(json['endTime'] as String), endTime: parseUtcToLocal(json['endTime'] as String),
attendanceStatus: AttendanceStatusType.fromJson(json['attendanceStatus'] as String?), attendanceStatus: AttendanceStatusType.fromJson(json['attendanceStatus'] as String?),
clientName: json['clientName'] as String? ?? '', clientName: json['clientName'] as String? ?? '',
hourlyRateCents: json['hourlyRateCents'] as int? ?? 0, hourlyRateCents: json['hourlyRateCents'] as int? ?? 0,
@@ -42,9 +43,7 @@ class TodayShift extends Equatable {
totalRateCents: json['totalRateCents'] as int? ?? 0, totalRateCents: json['totalRateCents'] as int? ?? 0,
totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0, totalRate: (json['totalRate'] as num?)?.toDouble() ?? 0.0,
locationAddress: json['locationAddress'] as String?, locationAddress: json['locationAddress'] as String?,
clockInAt: json['clockInAt'] != null clockInAt: tryParseUtcToLocal(json['clockInAt'] as String?),
? DateTime.parse(json['clockInAt'] as String)
: null,
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Membership status within a business. /// Membership status within a business.
enum BusinessMembershipStatus { enum BusinessMembershipStatus {
/// The user has been invited but has not accepted. /// The user has been invited but has not accepted.
@@ -63,8 +65,8 @@ class BusinessMembership extends Equatable {
businessName: json['businessName'] as String?, businessName: json['businessName'] as String?,
businessSlug: json['businessSlug'] as String?, businessSlug: json['businessSlug'] as String?,
metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{}, metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, createdAt: tryParseUtcToLocal(json['createdAt'] as String?),
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?),
); );
} }

View File

@@ -2,6 +2,8 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart' show OnboardingStatus, StaffStatus; import 'package:krow_domain/krow_domain.dart' show OnboardingStatus, StaffStatus;
import '../../core/utils/utc_parser.dart';
/// Represents a worker profile in the KROW platform. /// Represents a worker profile in the KROW platform.
/// ///
/// Maps to the V2 `staffs` table. Linked to a [User] via [userId]. /// 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?, workforceId: json['workforceId'] as String?,
vendorId: json['vendorId'] as String?, vendorId: json['vendorId'] as String?,
workforceNumber: json['workforceNumber'] as String?, workforceNumber: json['workforceNumber'] as String?,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, createdAt: tryParseUtcToLocal(json['createdAt'] as String?),
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?),
); );
} }

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import '../../core/utils/utc_parser.dart';
/// Account status for a platform user. /// Account status for a platform user.
enum UserStatus { enum UserStatus {
/// User is active and can sign in. /// User is active and can sign in.
@@ -37,8 +39,8 @@ class User extends Equatable {
phone: json['phone'] as String?, phone: json['phone'] as String?,
status: _parseUserStatus(json['status'] as String?), status: _parseUserStatus(json['status'] as String?),
metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{}, metadata: (json['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, createdAt: tryParseUtcToLocal(json['createdAt'] as String?),
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, updatedAt: tryParseUtcToLocal(json['updatedAt'] as String?),
); );
} }

View File

@@ -75,7 +75,7 @@ class ReorderWidget extends StatelessWidget {
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
), ),
child: const Icon( child: const Icon(
UiIcons.building, UiIcons.briefcase,
size: 16, size: 16,
color: UiColors.primary, color: UiColors.primary,
), ),
@@ -104,18 +104,6 @@ class ReorderWidget extends StatelessWidget {
], ],
), ),
), ),
// Column(
// crossAxisAlignment: CrossAxisAlignment.end,
// children: <Widget>[
// // 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), const SizedBox(height: UiConstants.space3),
@@ -130,7 +118,7 @@ class ReorderWidget extends StatelessWidget {
), ),
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
_Badge( _Badge(
icon: UiIcons.building, icon: UiIcons.users,
text: '${order.positionCount}', text: '${order.positionCount}',
color: UiColors.textSecondary, color: UiColors.textSecondary,
bg: UiColors.buttonSecondaryStill, bg: UiColors.buttonSecondaryStill,

View File

@@ -48,13 +48,13 @@ class OrderEditSheetState extends State<OrderEditSheet> {
_orderNameController = TextEditingController(text: widget.order.roleName); _orderNameController = TextEditingController(text: widget.order.roleName);
final String startHH = final String startHH =
widget.order.startsAt.toLocal().hour.toString().padLeft(2, '0'); widget.order.startsAt.hour.toString().padLeft(2, '0');
final String startMM = final String startMM =
widget.order.startsAt.toLocal().minute.toString().padLeft(2, '0'); widget.order.startsAt.minute.toString().padLeft(2, '0');
final String endHH = final String endHH =
widget.order.endsAt.toLocal().hour.toString().padLeft(2, '0'); widget.order.endsAt.hour.toString().padLeft(2, '0');
final String endMM = final String endMM =
widget.order.endsAt.toLocal().minute.toString().padLeft(2, '0'); widget.order.endsAt.minute.toString().padLeft(2, '0');
_positions = <Map<String, dynamic>>[ _positions = <Map<String, dynamic>>[
<String, dynamic>{ <String, dynamic>{

View File

@@ -77,9 +77,8 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
/// Formats a [DateTime] to a display time string (e.g. "9:00 AM"). /// Formats a [DateTime] to a display time string (e.g. "9:00 AM").
String _formatTime({required DateTime dateTime}) { String _formatTime({required DateTime dateTime}) {
final DateTime local = dateTime.toLocal(); final int hour24 = dateTime.hour;
final int hour24 = local.hour; final int minute = dateTime.minute;
final int minute = local.minute;
final String ampm = hour24 >= 12 ? 'PM' : 'AM'; final String ampm = hour24 >= 12 ? 'PM' : 'AM';
int hour = hour24 % 12; int hour = hour24 % 12;
if (hour == 0) hour = 12; if (hour == 0) hour = 12;