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

@@ -5,6 +5,7 @@ import 'package:krow_data_connect/krow_data_connect.dart';
import 'src/data/repositories_impl/home_repository_impl.dart';
import 'src/domain/repositories/home_repository_interface.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/presentation/blocs/client_home_bloc.dart';
import 'src/presentation/pages/client_home_page.dart';
@@ -24,17 +25,22 @@ class ClientHomeModule extends Module {
void binds(Injector i) {
// Repositories
i.addLazySingleton<HomeRepositoryInterface>(
() => HomeRepositoryImpl(i.get<HomeRepositoryMock>()),
() => HomeRepositoryImpl(
i.get<HomeRepositoryMock>(),
ExampleConnector.instance,
),
);
// UseCases
i.addLazySingleton(GetDashboardDataUseCase.new);
i.addLazySingleton(GetRecentReordersUseCase.new);
i.addLazySingleton(GetUserSessionDataUseCase.new);
// BLoCs
i.add<ClientHomeBloc>(
() => ClientHomeBloc(
getDashboardDataUseCase: i.get<GetDashboardDataUseCase>(),
getRecentReordersUseCase: i.get<GetRecentReordersUseCase>(),
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_domain/krow_domain.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).
class HomeRepositoryImpl implements HomeRepositoryInterface {
final HomeRepositoryMock _mock;
final ExampleConnector _dataConnect;
/// Creates a [HomeRepositoryImpl].
///
/// Requires a [HomeRepositoryMock] to perform data operations.
HomeRepositoryImpl(this._mock);
HomeRepositoryImpl(this._mock, this._dataConnect);
@override
Future<HomeDashboardData> getDashboardData() {
@@ -27,4 +29,62 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
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).
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:krow_domain/krow_domain.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 'client_home_event.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.
class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
final GetDashboardDataUseCase _getDashboardDataUseCase;
final GetRecentReordersUseCase _getRecentReordersUseCase;
final GetUserSessionDataUseCase _getUserSessionDataUseCase;
ClientHomeBloc({
required GetDashboardDataUseCase getDashboardDataUseCase,
required GetRecentReordersUseCase getRecentReordersUseCase,
required GetUserSessionDataUseCase getUserSessionDataUseCase,
}) : _getDashboardDataUseCase = getDashboardDataUseCase,
_getRecentReordersUseCase = getRecentReordersUseCase,
_getUserSessionDataUseCase = getUserSessionDataUseCase,
super(const ClientHomeState()) {
on<ClientHomeStarted>(_onStarted);
@@ -35,11 +39,13 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState> {
// Get dashboard data
final HomeDashboardData data = await _getDashboardDataUseCase();
final List<ReorderItem> reorderItems = await _getRecentReordersUseCase();
emit(
state.copyWith(
status: ClientHomeStatus.success,
dashboardData: data,
reorderItems: reorderItems,
businessName: sessionData.businessName,
photoUrl: sessionData.photoUrl,
),

View File

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

View File

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

View File

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