reoder view working

This commit is contained in:
José Salazar
2026-01-25 16:06:07 -05:00
parent 6e575a9ad0
commit a9a64caff6
15 changed files with 18338 additions and 17675 deletions

View File

@@ -1,16 +1,16 @@
# Basic Usage # Basic Usage
```dart ```dart
ExampleConnector.instance.createVendorBenefitPlan(createVendorBenefitPlanVariables).execute(); ExampleConnector.instance.createTeamMember(createTeamMemberVariables).execute();
ExampleConnector.instance.updateVendorBenefitPlan(updateVendorBenefitPlanVariables).execute(); ExampleConnector.instance.updateTeamMember(updateTeamMemberVariables).execute();
ExampleConnector.instance.deleteVendorBenefitPlan(deleteVendorBenefitPlanVariables).execute(); ExampleConnector.instance.updateTeamMemberInviteStatus(updateTeamMemberInviteStatusVariables).execute();
ExampleConnector.instance.createWorkforce(createWorkforceVariables).execute(); ExampleConnector.instance.acceptInviteByCode(acceptInviteByCodeVariables).execute();
ExampleConnector.instance.updateWorkforce(updateWorkforceVariables).execute(); ExampleConnector.instance.cancelInviteByCode(cancelInviteByCodeVariables).execute();
ExampleConnector.instance.deactivateWorkforce(deactivateWorkforceVariables).execute(); ExampleConnector.instance.deleteTeamMember(deleteTeamMemberVariables).execute();
ExampleConnector.instance.createApplication(createApplicationVariables).execute(); ExampleConnector.instance.listActivityLogs(listActivityLogsVariables).execute();
ExampleConnector.instance.updateApplicationStatus(updateApplicationStatusVariables).execute(); ExampleConnector.instance.getActivityLogById(getActivityLogByIdVariables).execute();
ExampleConnector.instance.deleteApplication(deleteApplicationVariables).execute(); ExampleConnector.instance.listActivityLogsByUserId(listActivityLogsByUserIdVariables).execute();
ExampleConnector.instance.listCertificates().execute(); ExampleConnector.instance.listUnreadActivityLogsByUserId(listUnreadActivityLogsByUserIdVariables).execute();
``` ```
@@ -23,8 +23,8 @@ Optional fields can be discovered based on classes that have `Optional` object t
This is an example of a mutation with an optional field: This is an example of a mutation with an optional field:
```dart ```dart
await ExampleConnector.instance.updateStaffDocument({ ... }) await ExampleConnector.instance.getRapidOrders({ ... })
.status(...) .offset(...)
.execute(); .execute();
``` ```

View File

@@ -0,0 +1,385 @@
part of 'generated.dart';
class ListShiftRolesByBusinessDateRangeCompletedOrdersVariablesBuilder {
String businessId;
Timestamp start;
Timestamp end;
Optional<int> _offset = Optional.optional(nativeFromJson, nativeToJson);
Optional<int> _limit = Optional.optional(nativeFromJson, nativeToJson);
final FirebaseDataConnect _dataConnect; ListShiftRolesByBusinessDateRangeCompletedOrdersVariablesBuilder offset(int? t) {
_offset.value = t;
return this;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersVariablesBuilder limit(int? t) {
_limit.value = t;
return this;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersVariablesBuilder(this._dataConnect, {required this.businessId,required this.start,required this.end,});
Deserializer<ListShiftRolesByBusinessDateRangeCompletedOrdersData> dataDeserializer = (dynamic json) => ListShiftRolesByBusinessDateRangeCompletedOrdersData.fromJson(jsonDecode(json));
Serializer<ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> varsSerializer = (ListShiftRolesByBusinessDateRangeCompletedOrdersVariables vars) => jsonEncode(vars.toJson());
Future<QueryResult<ListShiftRolesByBusinessDateRangeCompletedOrdersData, ListShiftRolesByBusinessDateRangeCompletedOrdersVariables>> execute() {
return ref().execute();
}
QueryRef<ListShiftRolesByBusinessDateRangeCompletedOrdersData, ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> ref() {
ListShiftRolesByBusinessDateRangeCompletedOrdersVariables vars= ListShiftRolesByBusinessDateRangeCompletedOrdersVariables(businessId: businessId,start: start,end: end,offset: _offset,limit: _limit,);
return _dataConnect.query("listShiftRolesByBusinessDateRangeCompletedOrders", dataDeserializer, varsSerializer, vars);
}
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles {
final String shiftId;
final String roleId;
final int count;
final int? assigned;
final double? hours;
final Timestamp? startTime;
final Timestamp? endTime;
final double? totalValue;
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole role;
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift shift;
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles.fromJson(dynamic json):
shiftId = nativeFromJson<String>(json['shiftId']),
roleId = nativeFromJson<String>(json['roleId']),
count = nativeFromJson<int>(json['count']),
assigned = json['assigned'] == null ? null : nativeFromJson<int>(json['assigned']),
hours = json['hours'] == null ? null : nativeFromJson<double>(json['hours']),
startTime = json['startTime'] == null ? null : Timestamp.fromJson(json['startTime']),
endTime = json['endTime'] == null ? null : Timestamp.fromJson(json['endTime']),
totalValue = json['totalValue'] == null ? null : nativeFromJson<double>(json['totalValue']),
role = ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole.fromJson(json['role']),
shift = ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift.fromJson(json['shift']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles;
return shiftId == otherTyped.shiftId &&
roleId == otherTyped.roleId &&
count == otherTyped.count &&
assigned == otherTyped.assigned &&
hours == otherTyped.hours &&
startTime == otherTyped.startTime &&
endTime == otherTyped.endTime &&
totalValue == otherTyped.totalValue &&
role == otherTyped.role &&
shift == otherTyped.shift;
}
@override
int get hashCode => Object.hashAll([shiftId.hashCode, roleId.hashCode, count.hashCode, assigned.hashCode, hours.hashCode, startTime.hashCode, endTime.hashCode, totalValue.hashCode, role.hashCode, shift.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['shiftId'] = nativeToJson<String>(shiftId);
json['roleId'] = nativeToJson<String>(roleId);
json['count'] = nativeToJson<int>(count);
if (assigned != null) {
json['assigned'] = nativeToJson<int?>(assigned);
}
if (hours != null) {
json['hours'] = nativeToJson<double?>(hours);
}
if (startTime != null) {
json['startTime'] = startTime!.toJson();
}
if (endTime != null) {
json['endTime'] = endTime!.toJson();
}
if (totalValue != null) {
json['totalValue'] = nativeToJson<double?>(totalValue);
}
json['role'] = role.toJson();
json['shift'] = shift.toJson();
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles({
required this.shiftId,
required this.roleId,
required this.count,
this.assigned,
this.hours,
this.startTime,
this.endTime,
this.totalValue,
required this.role,
required this.shift,
});
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole {
final String id;
final String name;
final double costPerHour;
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
name = nativeFromJson<String>(json['name']),
costPerHour = nativeFromJson<double>(json['costPerHour']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole;
return id == otherTyped.id &&
name == otherTyped.name &&
costPerHour == otherTyped.costPerHour;
}
@override
int get hashCode => Object.hashAll([id.hashCode, name.hashCode, costPerHour.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
json['name'] = nativeToJson<String>(name);
json['costPerHour'] = nativeToJson<double>(costPerHour);
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesRole({
required this.id,
required this.name,
required this.costPerHour,
});
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift {
final String id;
final Timestamp? date;
final String? location;
final String? locationAddress;
final String title;
final EnumValue<ShiftStatus>? status;
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder order;
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
date = json['date'] == null ? null : Timestamp.fromJson(json['date']),
location = json['location'] == null ? null : nativeFromJson<String>(json['location']),
locationAddress = json['locationAddress'] == null ? null : nativeFromJson<String>(json['locationAddress']),
title = nativeFromJson<String>(json['title']),
status = json['status'] == null ? null : shiftStatusDeserializer(json['status']),
order = ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder.fromJson(json['order']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift;
return id == otherTyped.id &&
date == otherTyped.date &&
location == otherTyped.location &&
locationAddress == otherTyped.locationAddress &&
title == otherTyped.title &&
status == otherTyped.status &&
order == otherTyped.order;
}
@override
int get hashCode => Object.hashAll([id.hashCode, date.hashCode, location.hashCode, locationAddress.hashCode, title.hashCode, status.hashCode, order.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
if (date != null) {
json['date'] = date!.toJson();
}
if (location != null) {
json['location'] = nativeToJson<String?>(location);
}
if (locationAddress != null) {
json['locationAddress'] = nativeToJson<String?>(locationAddress);
}
json['title'] = nativeToJson<String>(title);
if (status != null) {
json['status'] =
shiftStatusSerializer(status!)
;
}
json['order'] = order.toJson();
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShift({
required this.id,
this.date,
this.location,
this.locationAddress,
required this.title,
this.status,
required this.order,
});
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder {
final String id;
final EnumValue<OrderType> orderType;
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder.fromJson(dynamic json):
id = nativeFromJson<String>(json['id']),
orderType = orderTypeDeserializer(json['orderType']);
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder;
return id == otherTyped.id &&
orderType == otherTyped.orderType;
}
@override
int get hashCode => Object.hashAll([id.hashCode, orderType.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['id'] = nativeToJson<String>(id);
json['orderType'] =
orderTypeSerializer(orderType)
;
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRolesShiftOrder({
required this.id,
required this.orderType,
});
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersData {
final List<ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles> shiftRoles;
ListShiftRolesByBusinessDateRangeCompletedOrdersData.fromJson(dynamic json):
shiftRoles = (json['shiftRoles'] as List<dynamic>)
.map((e) => ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles.fromJson(e))
.toList();
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersData otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersData;
return shiftRoles == otherTyped.shiftRoles;
}
@override
int get hashCode => shiftRoles.hashCode;
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['shiftRoles'] = shiftRoles.map((e) => e.toJson()).toList();
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersData({
required this.shiftRoles,
});
}
@immutable
class ListShiftRolesByBusinessDateRangeCompletedOrdersVariables {
final String businessId;
final Timestamp start;
final Timestamp end;
late final Optional<int>offset;
late final Optional<int>limit;
@Deprecated('fromJson is deprecated for Variable classes as they are no longer required for deserialization.')
ListShiftRolesByBusinessDateRangeCompletedOrdersVariables.fromJson(Map<String, dynamic> json):
businessId = nativeFromJson<String>(json['businessId']),
start = Timestamp.fromJson(json['start']),
end = Timestamp.fromJson(json['end']) {
offset = Optional.optional(nativeFromJson, nativeToJson);
offset.value = json['offset'] == null ? null : nativeFromJson<int>(json['offset']);
limit = Optional.optional(nativeFromJson, nativeToJson);
limit.value = json['limit'] == null ? null : nativeFromJson<int>(json['limit']);
}
@override
bool operator ==(Object other) {
if(identical(this, other)) {
return true;
}
if(other.runtimeType != runtimeType) {
return false;
}
final ListShiftRolesByBusinessDateRangeCompletedOrdersVariables otherTyped = other as ListShiftRolesByBusinessDateRangeCompletedOrdersVariables;
return businessId == otherTyped.businessId &&
start == otherTyped.start &&
end == otherTyped.end &&
offset == otherTyped.offset &&
limit == otherTyped.limit;
}
@override
int get hashCode => Object.hashAll([businessId.hashCode, start.hashCode, end.hashCode, offset.hashCode, limit.hashCode]);
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json['businessId'] = nativeToJson<String>(businessId);
json['start'] = start.toJson();
json['end'] = end.toJson();
if(offset.state == OptionalState.set) {
json['offset'] = offset.toJson();
}
if(limit.state == OptionalState.set) {
json['limit'] = limit.toJson();
}
return json;
}
ListShiftRolesByBusinessDateRangeCompletedOrdersVariables({
required this.businessId,
required this.start,
required this.end,
required this.offset,
required this.limit,
});
}

View File

@@ -66,3 +66,4 @@ export 'src/entities/support/working_area.dart';
// Home // Home
export 'src/entities/home/home_dashboard_data.dart'; export 'src/entities/home/home_dashboard_data.dart';
export 'src/entities/home/reorder_item.dart';

View File

@@ -0,0 +1,46 @@
import 'package:equatable/equatable.dart';
/// Summary of a completed shift role used for reorder suggestions.
class ReorderItem extends Equatable {
const ReorderItem({
required this.orderId,
required this.title,
required this.location,
required this.hourlyRate,
required this.hours,
required this.workers,
required this.type,
});
/// Parent order id for the completed shift.
final String orderId;
/// Display title (role + shift title).
final String title;
/// Location from the shift.
final String location;
/// Hourly rate from the role.
final double hourlyRate;
/// Total hours for the shift role.
final double hours;
/// Worker count for the shift role.
final int workers;
/// Order type (e.g., ONE_TIME).
final String type;
@override
List<Object?> get props => <Object?>[
orderId,
title,
location,
hourlyRate,
hours,
workers,
type,
];
}

View File

@@ -5,6 +5,7 @@ import 'package:krow_data_connect/krow_data_connect.dart';
import 'src/data/repositories_impl/home_repository_impl.dart'; import 'src/data/repositories_impl/home_repository_impl.dart';
import 'src/domain/repositories/home_repository_interface.dart'; import 'src/domain/repositories/home_repository_interface.dart';
import 'src/domain/usecases/get_dashboard_data_usecase.dart'; import 'src/domain/usecases/get_dashboard_data_usecase.dart';
import 'src/domain/usecases/get_recent_reorders_usecase.dart';
import 'src/domain/usecases/get_user_session_data_usecase.dart'; import 'src/domain/usecases/get_user_session_data_usecase.dart';
import 'src/presentation/blocs/client_home_bloc.dart'; import 'src/presentation/blocs/client_home_bloc.dart';
import 'src/presentation/pages/client_home_page.dart'; import 'src/presentation/pages/client_home_page.dart';
@@ -24,17 +25,22 @@ class ClientHomeModule extends Module {
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addLazySingleton<HomeRepositoryInterface>( i.addLazySingleton<HomeRepositoryInterface>(
() => HomeRepositoryImpl(i.get<HomeRepositoryMock>()), () => HomeRepositoryImpl(
i.get<HomeRepositoryMock>(),
ExampleConnector.instance,
),
); );
// UseCases // UseCases
i.addLazySingleton(GetDashboardDataUseCase.new); i.addLazySingleton(GetDashboardDataUseCase.new);
i.addLazySingleton(GetRecentReordersUseCase.new);
i.addLazySingleton(GetUserSessionDataUseCase.new); i.addLazySingleton(GetUserSessionDataUseCase.new);
// BLoCs // BLoCs
i.add<ClientHomeBloc>( i.add<ClientHomeBloc>(
() => ClientHomeBloc( () => ClientHomeBloc(
getDashboardDataUseCase: i.get<GetDashboardDataUseCase>(), getDashboardDataUseCase: i.get<GetDashboardDataUseCase>(),
getRecentReordersUseCase: i.get<GetRecentReordersUseCase>(),
getUserSessionDataUseCase: i.get<GetUserSessionDataUseCase>(), getUserSessionDataUseCase: i.get<GetUserSessionDataUseCase>(),
), ),
); );

View File

@@ -1,3 +1,4 @@
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/home_repository_interface.dart'; import '../../domain/repositories/home_repository_interface.dart';
@@ -8,11 +9,12 @@ import '../../domain/repositories/home_repository_interface.dart';
/// domain layer and the data source (in this case, a mock from data_connect). /// domain layer and the data source (in this case, a mock from data_connect).
class HomeRepositoryImpl implements HomeRepositoryInterface { class HomeRepositoryImpl implements HomeRepositoryInterface {
final HomeRepositoryMock _mock; final HomeRepositoryMock _mock;
final ExampleConnector _dataConnect;
/// Creates a [HomeRepositoryImpl]. /// Creates a [HomeRepositoryImpl].
/// ///
/// Requires a [HomeRepositoryMock] to perform data operations. /// Requires a [HomeRepositoryMock] to perform data operations.
HomeRepositoryImpl(this._mock); HomeRepositoryImpl(this._mock, this._dataConnect);
@override @override
Future<HomeDashboardData> getDashboardData() { Future<HomeDashboardData> getDashboardData() {
@@ -27,4 +29,62 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
photoUrl: photoUrl, photoUrl: photoUrl,
); );
} }
@override
Future<List<ReorderItem>> getRecentReorders() async {
final String? businessId = ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return const <ReorderItem>[];
}
final DateTime now = DateTime.now();
final DateTime start = now.subtract(const Duration(days: 30));
final fdc.Timestamp startTimestamp = _toTimestamp(start);
final fdc.Timestamp endTimestamp = _toTimestamp(now);
final fdc.QueryResult<
ListShiftRolesByBusinessDateRangeCompletedOrdersData,
ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> result =
await _dataConnect.listShiftRolesByBusinessDateRangeCompletedOrders(
businessId: businessId,
start: startTimestamp,
end: endTimestamp,
).execute();
print(
'Home reorder: completed shiftRoles=${result.data.shiftRoles.length}',
);
return result.data.shiftRoles.map((
ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole,
) {
print(
'Home reorder item: orderId=${shiftRole.shift.order.id} '
'shiftId=${shiftRole.shiftId} roleId=${shiftRole.roleId} '
'orderType=${shiftRole.shift.order.orderType.stringValue} '
'hours=${shiftRole.hours} count=${shiftRole.count}',
);
final String location =
shiftRole.shift.location ??
shiftRole.shift.locationAddress ??
'';
final String type = shiftRole.shift.order.orderType.stringValue;
return ReorderItem(
orderId: shiftRole.shift.order.id,
title: '${shiftRole.role.name} - ${shiftRole.shift.title}',
location: location,
hourlyRate: shiftRole.role.costPerHour,
hours: shiftRole.hours ?? 0,
workers: shiftRole.count,
type: type,
);
}).toList();
}
fdc.Timestamp _toTimestamp(DateTime date) {
final int millis = date.millisecondsSinceEpoch;
final int seconds = millis ~/ 1000;
final int nanos = (millis % 1000) * 1000000;
return fdc.Timestamp(nanos, seconds);
}
} }

View File

@@ -25,4 +25,7 @@ abstract interface class HomeRepositoryInterface {
/// Fetches the user's session data (business name and photo). /// Fetches the user's session data (business name and photo).
UserSessionData getUserSessionData(); UserSessionData getUserSessionData();
/// Fetches recently completed shift roles for reorder suggestions.
Future<List<ReorderItem>> getRecentReorders();
} }

View File

@@ -0,0 +1,16 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/home_repository_interface.dart';
/// Use case to fetch recent completed shift roles for reorder suggestions.
class GetRecentReordersUseCase implements NoInputUseCase<List<ReorderItem>> {
final HomeRepositoryInterface _repository;
/// Creates a [GetRecentReordersUseCase].
GetRecentReordersUseCase(this._repository);
@override
Future<List<ReorderItem>> call() {
return _repository.getRecentReorders();
}
}

View File

@@ -2,6 +2,7 @@ import 'package:client_home/src/domain/repositories/home_repository_interface.da
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/usecases/get_dashboard_data_usecase.dart'; import '../../domain/usecases/get_dashboard_data_usecase.dart';
import '../../domain/usecases/get_recent_reorders_usecase.dart';
import '../../domain/usecases/get_user_session_data_usecase.dart'; import '../../domain/usecases/get_user_session_data_usecase.dart';
import 'client_home_event.dart'; import 'client_home_event.dart';
import 'client_home_state.dart'; import 'client_home_state.dart';
@@ -9,12 +10,15 @@ import 'client_home_state.dart';
/// BLoC responsible for managing the state and business logic of the client home dashboard. /// BLoC responsible for managing the state and business logic of the client home dashboard.
class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> { class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
final GetDashboardDataUseCase _getDashboardDataUseCase; final GetDashboardDataUseCase _getDashboardDataUseCase;
final GetRecentReordersUseCase _getRecentReordersUseCase;
final GetUserSessionDataUseCase _getUserSessionDataUseCase; final GetUserSessionDataUseCase _getUserSessionDataUseCase;
ClientHomeBloc({ ClientHomeBloc({
required GetDashboardDataUseCase getDashboardDataUseCase, required GetDashboardDataUseCase getDashboardDataUseCase,
required GetRecentReordersUseCase getRecentReordersUseCase,
required GetUserSessionDataUseCase getUserSessionDataUseCase, required GetUserSessionDataUseCase getUserSessionDataUseCase,
}) : _getDashboardDataUseCase = getDashboardDataUseCase, }) : _getDashboardDataUseCase = getDashboardDataUseCase,
_getRecentReordersUseCase = getRecentReordersUseCase,
_getUserSessionDataUseCase = getUserSessionDataUseCase, _getUserSessionDataUseCase = getUserSessionDataUseCase,
super(const ClientHomeState()) { super(const ClientHomeState()) {
on<ClientHomeStarted>(_onStarted); on<ClientHomeStarted>(_onStarted);
@@ -35,11 +39,13 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
// Get dashboard data // Get dashboard data
final HomeDashboardData data = await _getDashboardDataUseCase(); final HomeDashboardData data = await _getDashboardDataUseCase();
final List<ReorderItem> reorderItems = await _getRecentReordersUseCase();
emit( emit(
state.copyWith( state.copyWith(
status: ClientHomeStatus.success, status: ClientHomeStatus.success,
dashboardData: data, dashboardData: data,
reorderItems: reorderItems,
businessName: sessionData.businessName, businessName: sessionData.businessName,
photoUrl: sessionData.photoUrl, photoUrl: sessionData.photoUrl,
), ),

View File

@@ -12,6 +12,7 @@ class ClientHomeState extends Equatable {
final bool isEditMode; final bool isEditMode;
final String? errorMessage; final String? errorMessage;
final HomeDashboardData dashboardData; final HomeDashboardData dashboardData;
final List<ReorderItem> reorderItems;
final String businessName; final String businessName;
final String? photoUrl; final String? photoUrl;
@@ -19,9 +20,11 @@ class ClientHomeState extends Equatable {
this.status = ClientHomeStatus.initial, this.status = ClientHomeStatus.initial,
this.widgetOrder = const <String>[ this.widgetOrder = const <String>[
'actions', 'actions',
'reorder',
], ],
this.widgetVisibility = const <String, bool>{ this.widgetVisibility = const <String, bool>{
'actions': true, 'actions': true,
'reorder': true,
}, },
this.isEditMode = false, this.isEditMode = false,
this.errorMessage, this.errorMessage,
@@ -33,6 +36,7 @@ class ClientHomeState extends Equatable {
totalNeeded: 10, totalNeeded: 10,
totalFilled: 8, totalFilled: 8,
), ),
this.reorderItems = const <ReorderItem>[],
this.businessName = 'Your Company', this.businessName = 'Your Company',
this.photoUrl, this.photoUrl,
}); });
@@ -44,6 +48,7 @@ class ClientHomeState extends Equatable {
bool? isEditMode, bool? isEditMode,
String? errorMessage, String? errorMessage,
HomeDashboardData? dashboardData, HomeDashboardData? dashboardData,
List<ReorderItem>? reorderItems,
String? businessName, String? businessName,
String? photoUrl, String? photoUrl,
}) { }) {
@@ -54,6 +59,7 @@ class ClientHomeState extends Equatable {
isEditMode: isEditMode ?? this.isEditMode, isEditMode: isEditMode ?? this.isEditMode,
errorMessage: errorMessage ?? this.errorMessage, errorMessage: errorMessage ?? this.errorMessage,
dashboardData: dashboardData ?? this.dashboardData, dashboardData: dashboardData ?? this.dashboardData,
reorderItems: reorderItems ?? this.reorderItems,
businessName: businessName ?? this.businessName, businessName: businessName ?? this.businessName,
photoUrl: photoUrl ?? this.photoUrl, photoUrl: photoUrl ?? this.photoUrl,
); );
@@ -67,6 +73,7 @@ class ClientHomeState extends Equatable {
isEditMode, isEditMode,
errorMessage, errorMessage,
dashboardData, dashboardData,
reorderItems,
businessName, businessName,
photoUrl, photoUrl,
]; ];

View File

@@ -64,6 +64,7 @@ class DashboardWidgetBuilder extends StatelessWidget {
); );
case 'reorder': case 'reorder':
return ReorderWidget( return ReorderWidget(
orders: state.reorderItems,
onReorderPressed: (Map<String, dynamic> data) { onReorderPressed: (Map<String, dynamic> data) {
ClientHomeSheets.showOrderFormSheet( ClientHomeSheets.showOrderFormSheet(
context, context,

View File

@@ -1,46 +1,28 @@
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
/// A widget that allows clients to reorder recent shifts. /// A widget that allows clients to reorder recent shifts.
class ReorderWidget extends StatelessWidget { class ReorderWidget extends StatelessWidget {
/// Recent completed orders for reorder.
final List<ReorderItem> orders;
/// Callback when a reorder button is pressed. /// Callback when a reorder button is pressed.
final Function(Map<String, dynamic> shiftData) onReorderPressed; final Function(Map<String, dynamic> shiftData) onReorderPressed;
/// Creates a [ReorderWidget]. /// Creates a [ReorderWidget].
const ReorderWidget({super.key, required this.onReorderPressed}); const ReorderWidget({
super.key,
required this.orders,
required this.onReorderPressed,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TranslationsClientHomeReorderEn i18n = t.client_home.reorder; final TranslationsClientHomeReorderEn i18n = t.client_home.reorder;
// Mock recent orders final List<ReorderItem> recentOrders = orders;
final List<Map<String, Object>> recentOrders = <Map<String, Object>>[
<String, Object>{
'title': 'Server',
'location': 'Downtown Restaurant',
'hourlyRate': 18.0,
'hours': 6,
'workers': 3,
'type': 'One Day',
},
<String, Object>{
'title': 'Bartender',
'location': 'Rooftop Bar',
'hourlyRate': 22.0,
'hours': 7,
'workers': 2,
'type': 'One Day',
},
<String, Object>{
'title': 'Event Staff',
'location': 'Convention Center',
'hourlyRate': 20.0,
'hours': 10,
'workers': 5,
'type': 'Multi-Day',
},
];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -60,11 +42,9 @@ class ReorderWidget extends StatelessWidget {
separatorBuilder: (BuildContext context, int index) => separatorBuilder: (BuildContext context, int index) =>
const SizedBox(width: UiConstants.space3), const SizedBox(width: UiConstants.space3),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final Map<String, Object> order = recentOrders[index]; final ReorderItem order = recentOrders[index];
final double totalCost = final double totalCost =
(order['hourlyRate'] as double) * order.hourlyRate * order.hours * order.workers;
(order['hours'] as int) *
(order['workers'] as int);
return Container( return Container(
width: 260, width: 260,
@@ -110,12 +90,12 @@ class ReorderWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
order['title'] as String, order.title,
style: UiTypography.body2b, style: UiTypography.body2b,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Text( Text(
order['location'] as String, order.location,
style: style:
UiTypography.footnote1r.textSecondary, UiTypography.footnote1r.textSecondary,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -135,9 +115,9 @@ class ReorderWidget extends StatelessWidget {
), ),
Text( Text(
i18n.per_hr( i18n.per_hr(
amount: order['hourlyRate'].toString(), amount: order.hourlyRate.toString(),
) + ) +
' · ${order['hours']}h', ' · ${order.hours}h',
style: UiTypography.footnote2r.textSecondary, style: UiTypography.footnote2r.textSecondary,
), ),
], ],
@@ -149,7 +129,7 @@ class ReorderWidget extends StatelessWidget {
children: <Widget>[ children: <Widget>[
_Badge( _Badge(
icon: UiIcons.success, icon: UiIcons.success,
text: order['type'] as String, text: order.type,
color: const Color(0xFF2563EB), color: const Color(0xFF2563EB),
bg: const Color(0xFF2563EB), bg: const Color(0xFF2563EB),
textColor: UiColors.white, textColor: UiColors.white,
@@ -157,7 +137,7 @@ class ReorderWidget extends StatelessWidget {
const SizedBox(width: UiConstants.space2), const SizedBox(width: UiConstants.space2),
_Badge( _Badge(
icon: UiIcons.building, icon: UiIcons.building,
text: '${order['workers']}', text: '${order.workers}',
color: const Color(0xFF334155), color: const Color(0xFF334155),
bg: const Color(0xFFF1F5F9), bg: const Color(0xFFF1F5F9),
textColor: const Color(0xFF334155), textColor: const Color(0xFF334155),
@@ -169,7 +149,15 @@ class ReorderWidget extends StatelessWidget {
height: 28, height: 28,
width: double.infinity, width: double.infinity,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => onReorderPressed(order), onPressed: () => onReorderPressed(<String, dynamic>{
'orderId': order.orderId,
'title': order.title,
'location': order.location,
'hourlyRate': order.hourlyRate,
'hours': order.hours,
'workers': order.workers,
'type': order.type,
}),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
foregroundColor: UiColors.white, foregroundColor: UiColors.white,

View File

@@ -382,3 +382,56 @@ query listShiftRolesByBusinessAndOrder(
} }
} }
} }
#reorder get list by businessId
query listShiftRolesByBusinessDateRangeCompletedOrders(
$businessId: UUID!
$start: Timestamp!
$end: Timestamp!
$offset: Int
$limit: Int
) @auth(level: USER) {
shiftRoles(
where: {
shift: {
date: { ge: $start, le: $end }
order: {
businessId: { eq: $businessId }
status: { eq: COMPLETED }
}
}
}
offset: $offset
limit: $limit
orderBy: { createdAt: DESC }
) {
shiftId
roleId
count
assigned
hours
startTime
endTime
totalValue
role {
id
name
costPerHour
}
shift {
id
date
location
locationAddress
title
status
order {
id
orderType
}
}
}
}