Merge pull request #428 from Oloodi/408-feature-implement-paidunpaid-breaks---client-app-frontend-development

DataConnectorService is implemented in the client appplication
This commit is contained in:
Achintha Isuru
2026-02-16 17:57:53 -05:00
committed by GitHub
15 changed files with 277 additions and 441 deletions

View File

@@ -41,7 +41,7 @@ class ClientSignUpPage extends StatelessWidget {
final TranslationsClientAuthenticationSignUpPageEn i18n = t.client_authentication.sign_up_page; final TranslationsClientAuthenticationSignUpPageEn i18n = t.client_authentication.sign_up_page;
final ClientAuthBloc authBloc = Modular.get<ClientAuthBloc>(); final ClientAuthBloc authBloc = Modular.get<ClientAuthBloc>();
return BlocProvider.value( return BlocProvider<ClientAuthBloc>.value(
value: authBloc, value: authBloc,
child: BlocConsumer<ClientAuthBloc, ClientAuthState>( child: BlocConsumer<ClientAuthBloc, ClientAuthState>(
listener: (BuildContext context, ClientAuthState state) { listener: (BuildContext context, ClientAuthState state) {

View File

@@ -54,7 +54,6 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
_getSpendingBreakdown.call(state.period), _getSpendingBreakdown.call(state.period),
]); ]);
final double currentBill = results[0] as double;
final double savings = results[1] as double; final double savings = results[1] as double;
final List<Invoice> pendingInvoices = results[2] as List<Invoice>; final List<Invoice> pendingInvoices = results[2] as List<Invoice>;
final List<Invoice> invoiceHistory = results[3] as List<Invoice>; final List<Invoice> invoiceHistory = results[3] as List<Invoice>;

View File

@@ -10,24 +10,20 @@ import 'presentation/pages/coverage_page.dart';
/// Modular module for the coverage feature. /// Modular module for the coverage feature.
class CoverageModule extends Module { class CoverageModule extends Module {
@override
List<Module> get imports => <Module>[DataConnectModule()];
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addSingleton<CoverageRepository>( i.addSingleton<CoverageRepository>(CoverageRepositoryImpl.new);
() => CoverageRepositoryImpl(dataConnect: ExampleConnector.instance),
);
// Use Cases // Use Cases
i.addSingleton(GetShiftsForDateUseCase.new); i.addSingleton(GetShiftsForDateUseCase.new);
i.addSingleton(GetCoverageStatsUseCase.new); i.addSingleton(GetCoverageStatsUseCase.new);
// BLoCs // BLoCs
i.addSingleton<CoverageBloc>( i.addSingleton<CoverageBloc>(CoverageBloc.new);
() => CoverageBloc(
getShiftsForDate: i.get<GetShiftsForDateUseCase>(),
getCoverageStats: i.get<GetCoverageStatsUseCase>(),
),
);
} }
@override @override

View File

@@ -15,44 +15,36 @@ import '../../domain/repositories/coverage_repository.dart';
/// - Returns domain entities from `domain/ui_entities`. /// - Returns domain entities from `domain/ui_entities`.
class CoverageRepositoryImpl implements CoverageRepository { class CoverageRepositoryImpl implements CoverageRepository {
/// Creates a [CoverageRepositoryImpl]. /// Creates a [CoverageRepositoryImpl].
CoverageRepositoryImpl({required dc.ExampleConnector dataConnect}) CoverageRepositoryImpl({required dc.DataConnectService service}) : _service = service;
: _dataConnect = dataConnect;
final dc.ExampleConnector _dataConnect; final dc.DataConnectService _service;
/// Fetches shifts for a specific date. /// Fetches shifts for a specific date.
@override @override
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async { Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
try { return _service.run(() async {
final String? businessId = final String businessId = await _service.getBusinessId();
dc.ClientSessionStore.instance.session?.business?.id;
if (businessId == null || businessId.isEmpty) {
return <CoverageShift>[];
}
final DateTime start = DateTime(date.year, date.month, date.day); final DateTime start = DateTime(date.year, date.month, date.day);
final DateTime end = final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
final fdc.QueryResult< final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult = dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult =
await _dataConnect await _service.connector
.listShiftRolesByBusinessAndDateRange( .listShiftRolesByBusinessAndDateRange(
businessId: businessId, businessId: businessId,
start: _toTimestamp(start), start: _service.toTimestamp(start),
end: _toTimestamp(end), end: _service.toTimestamp(end),
) )
.execute(); .execute();
final fdc.QueryResult< final fdc.QueryResult<dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayData,
dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult = dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult =
await _dataConnect await _service.connector
.listStaffsApplicationsByBusinessForDay( .listStaffsApplicationsByBusinessForDay(
businessId: businessId, businessId: businessId,
dayStart: _toTimestamp(start), dayStart: _service.toTimestamp(start),
dayEnd: _toTimestamp(end), dayEnd: _service.toTimestamp(end),
) )
.execute(); .execute();
@@ -61,18 +53,7 @@ class CoverageRepositoryImpl implements CoverageRepository {
applicationsResult.data.applications, applicationsResult.data.applications,
date, date,
); );
} catch (e) { });
final String error = e.toString().toLowerCase();
if (error.contains('network') ||
error.contains('connection') ||
error.contains('unavailable') ||
error.contains('offline') ||
error.contains('socket') ||
error.contains('failed host lookup')) {
throw NetworkException(technicalMessage: 'Coverage fetch failed: $e');
}
throw ServerException(technicalMessage: 'Coverage fetch failed: $e');
}
} }
/// Fetches coverage statistics for a specific date. /// Fetches coverage statistics for a specific date.
@@ -110,14 +91,6 @@ class CoverageRepositoryImpl implements CoverageRepository {
); );
} }
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final DateTime utc = dateTime.toUtc();
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds =
(utc.millisecondsSinceEpoch % 1000) * 1000000;
return fdc.Timestamp(nanoseconds, seconds);
}
List<CoverageShift> _mapCoverageShifts( List<CoverageShift> _mapCoverageShifts(
List<dc.ListShiftRolesByBusinessAndDateRangeShiftRoles> shiftRoles, List<dc.ListShiftRolesByBusinessAndDateRangeShiftRoles> shiftRoles,
List<dc.ListStaffsApplicationsByBusinessForDayApplications> applications, List<dc.ListStaffsApplicationsByBusinessForDayApplications> applications,

View File

@@ -6,8 +6,7 @@ import '../../domain/repositories/client_create_order_repository_interface.dart'
/// Implementation of [ClientCreateOrderRepositoryInterface]. /// Implementation of [ClientCreateOrderRepositoryInterface].
/// ///
/// This implementation coordinates data access for order creation by delegating /// This implementation coordinates data access for order creation by [DataConnectService] from the shared
/// to the [OrderRepositoryMock] and [ExampleConnector] from the shared
/// Data Connect package. /// Data Connect package.
/// ///
/// It follows the KROW Clean Architecture by keeping the data layer focused /// It follows the KROW Clean Architecture by keeping the data layer focused

View File

@@ -23,11 +23,7 @@ class ClientHomeModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addLazySingleton<HomeRepositoryInterface>( i.addLazySingleton<HomeRepositoryInterface>(HomeRepositoryImpl.new);
() => HomeRepositoryImpl(
ExampleConnector.instance,
),
);
// UseCases // UseCases
i.addLazySingleton(GetDashboardDataUseCase.new); i.addLazySingleton(GetDashboardDataUseCase.new);
@@ -35,13 +31,7 @@ class ClientHomeModule extends Module {
i.addLazySingleton(GetUserSessionDataUseCase.new); i.addLazySingleton(GetUserSessionDataUseCase.new);
// BLoCs // BLoCs
i.add<ClientHomeBloc>( i.add<ClientHomeBloc>(ClientHomeBloc.new);
() => ClientHomeBloc(
getDashboardDataUseCase: i.get<GetDashboardDataUseCase>(),
getRecentReordersUseCase: i.get<GetRecentReordersUseCase>(),
getUserSessionDataUseCase: i.get<GetUserSessionDataUseCase>(),
),
);
} }
@override @override

View File

@@ -8,25 +8,14 @@ import '../../domain/repositories/home_repository_interface.dart';
/// This implementation resides in the data layer and acts as a bridge between the /// This implementation resides in the data layer and acts as a bridge between the
/// 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 {
/// Creates a [HomeRepositoryImpl]. /// Creates a [HomeRepositoryImpl].
HomeRepositoryImpl(this._dataConnect); HomeRepositoryImpl(this._service);
final dc.ExampleConnector _dataConnect; final dc.DataConnectService _service;
@override @override
Future<HomeDashboardData> getDashboardData() async { Future<HomeDashboardData> getDashboardData() async {
try { return _service.run(() async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; final String businessId = await _service.getBusinessId();
if (businessId == null || businessId.isEmpty) {
return const HomeDashboardData(
weeklySpending: 0,
next7DaysSpending: 0,
weeklyShifts: 0,
next7DaysScheduled: 0,
totalNeeded: 0,
totalFilled: 0,
);
}
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
final int daysFromMonday = now.weekday - DateTime.monday; final int daysFromMonday = now.weekday - DateTime.monday;
@@ -35,14 +24,13 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
final DateTime weekRangeStart = DateTime(monday.year, monday.month, monday.day); final DateTime weekRangeStart = DateTime(monday.year, monday.month, monday.day);
final DateTime weekRangeEnd = final DateTime weekRangeEnd =
DateTime(monday.year, monday.month, monday.day + 13, 23, 59, 59, 999); DateTime(monday.year, monday.month, monday.day + 13, 23, 59, 59, 999);
final fdc.QueryResult< final fdc.QueryResult<dc.GetCompletedShiftsByBusinessIdData,
dc.GetCompletedShiftsByBusinessIdData,
dc.GetCompletedShiftsByBusinessIdVariables> completedResult = dc.GetCompletedShiftsByBusinessIdVariables> completedResult =
await _dataConnect await _service.connector
.getCompletedShiftsByBusinessId( .getCompletedShiftsByBusinessId(
businessId: businessId, businessId: businessId,
dateFrom: _toTimestamp(weekRangeStart), dateFrom: _service.toTimestamp(weekRangeStart),
dateTo: _toTimestamp(weekRangeEnd), dateTo: _service.toTimestamp(weekRangeEnd),
) )
.execute(); .execute();
@@ -50,8 +38,7 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
double next7DaysSpending = 0.0; double next7DaysSpending = 0.0;
int weeklyShifts = 0; int weeklyShifts = 0;
int next7DaysScheduled = 0; int next7DaysScheduled = 0;
for (final dc.GetCompletedShiftsByBusinessIdShifts shift for (final dc.GetCompletedShiftsByBusinessIdShifts shift in completedResult.data.shifts) {
in completedResult.data.shifts) {
final DateTime? shiftDate = shift.date?.toDateTime(); final DateTime? shiftDate = shift.date?.toDateTime();
if (shiftDate == null) { if (shiftDate == null) {
continue; continue;
@@ -73,14 +60,13 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
final DateTime start = DateTime(now.year, now.month, now.day); final DateTime start = DateTime(now.year, now.month, now.day);
final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999); final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999);
final fdc.QueryResult< final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> result = dc.ListShiftRolesByBusinessAndDateRangeVariables> result =
await _dataConnect await _service.connector
.listShiftRolesByBusinessAndDateRange( .listShiftRolesByBusinessAndDateRange(
businessId: businessId, businessId: businessId,
start: _toTimestamp(start), start: _service.toTimestamp(start),
end: _toTimestamp(end), end: _service.toTimestamp(end),
) )
.execute(); .execute();
@@ -100,18 +86,7 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
totalNeeded: totalNeeded, totalNeeded: totalNeeded,
totalFilled: totalFilled, totalFilled: totalFilled,
); );
} catch (e) { });
final String error = e.toString().toLowerCase();
if (error.contains('network') ||
error.contains('connection') ||
error.contains('unavailable') ||
error.contains('offline') ||
error.contains('socket') ||
error.contains('failed host lookup')) {
throw NetworkException(technicalMessage: 'Home dashboard fetch failed: $e');
}
throw ServerException(technicalMessage: 'Home dashboard fetch failed: $e');
}
} }
@override @override
@@ -125,34 +100,29 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
@override @override
Future<List<ReorderItem>> getRecentReorders() async { Future<List<ReorderItem>> getRecentReorders() async {
try { return _service.run(() async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; final String businessId = await _service.getBusinessId();
if (businessId == null || businessId.isEmpty) {
return const <ReorderItem>[];
}
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
final DateTime start = now.subtract(const Duration(days: 30)); final DateTime start = now.subtract(const Duration(days: 30));
final fdc.Timestamp startTimestamp = _toTimestamp(start); final fdc.Timestamp startTimestamp = _service.toTimestamp(start);
final fdc.Timestamp endTimestamp = _toTimestamp(now); final fdc.Timestamp endTimestamp = _service.toTimestamp(now);
final fdc.QueryResult< final fdc.QueryResult<dc.ListShiftRolesByBusinessDateRangeCompletedOrdersData,
dc.ListShiftRolesByBusinessDateRangeCompletedOrdersData,
dc.ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> result = dc.ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> result =
await _dataConnect.listShiftRolesByBusinessDateRangeCompletedOrders( await _service.connector
.listShiftRolesByBusinessDateRangeCompletedOrders(
businessId: businessId, businessId: businessId,
start: startTimestamp, start: startTimestamp,
end: endTimestamp, end: endTimestamp,
).execute(); )
.execute();
return result.data.shiftRoles.map(( return result.data.shiftRoles
.map((
dc.ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole, dc.ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole,
) { ) {
final String location = shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? '';
final String location =
shiftRole.shift.location ??
shiftRole.shift.locationAddress ??
'';
final String type = shiftRole.shift.order.orderType.stringValue; final String type = shiftRole.shift.order.orderType.stringValue;
return ReorderItem( return ReorderItem(
orderId: shiftRole.shift.order.id, orderId: shiftRole.shift.order.id,
@@ -163,26 +133,8 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
workers: shiftRole.count, workers: shiftRole.count,
type: type, type: type,
); );
}).toList(); })
} catch (e) { .toList();
final String error = e.toString().toLowerCase(); });
if (error.contains('network') ||
error.contains('connection') ||
error.contains('unavailable') ||
error.contains('offline') ||
error.contains('socket') ||
error.contains('failed host lookup')) {
throw NetworkException(technicalMessage: 'Home reorders fetch failed: $e');
}
throw ServerException(technicalMessage: 'Home reorders fetch failed: $e');
}
}
fdc.Timestamp _toTimestamp(DateTime date) {
final DateTime utc = date.toUtc();
final int millis = utc.millisecondsSinceEpoch;
final int seconds = millis ~/ 1000;
final int nanos = (millis % 1000) * 1000000;
return fdc.Timestamp(nanos, seconds);
} }
} }

View File

@@ -22,8 +22,8 @@ class CoverageDashboard extends StatelessWidget {
int totalConfirmed = 0; int totalConfirmed = 0;
double todayCost = 0; double todayCost = 0;
for (final s in shifts) { for (final dynamic s in shifts) {
final int needed = s['workersNeeded'] as int? ?? 0; final int needed = (s as Map<String, dynamic>)['workersNeeded'] as int? ?? 0;
final int confirmed = s['filled'] as int? ?? 0; final int confirmed = s['filled'] as int? ?? 0;
final double rate = s['hourlyRate'] as double? ?? 0.0; final double rate = s['hourlyRate'] as double? ?? 0.0;
final double hours = s['hours'] as double? ?? 0.0; final double hours = s['hours'] as double? ?? 0.0;
@@ -39,10 +39,10 @@ class CoverageDashboard extends StatelessWidget {
final int unfilledPositions = totalNeeded - totalConfirmed; final int unfilledPositions = totalNeeded - totalConfirmed;
final int checkedInCount = applications final int checkedInCount = applications
.where((a) => (a as Map)['checkInTime'] != null) .where((dynamic a) => (a as Map<String, dynamic>)['checkInTime'] != null)
.length; .length;
final int lateWorkersCount = applications final int lateWorkersCount = applications
.where((a) => (a as Map)['status'] == 'LATE') .where((dynamic a) => (a as Map<String, dynamic>)['status'] == 'LATE')
.length; .length;
final bool isCoverageGood = coveragePercent >= 90; final bool isCoverageGood = coveragePercent >= 90;

View File

@@ -3,7 +3,6 @@ library;
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'src/data/repositories_impl/hub_repository_impl.dart'; import 'src/data/repositories_impl/hub_repository_impl.dart';
import 'src/domain/repositories/hub_repository_interface.dart'; import 'src/domain/repositories/hub_repository_interface.dart';
import 'src/domain/usecases/assign_nfc_tag_usecase.dart'; import 'src/domain/usecases/assign_nfc_tag_usecase.dart';
@@ -23,12 +22,7 @@ class ClientHubsModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addLazySingleton<HubRepositoryInterface>( i.addLazySingleton<HubRepositoryInterface>(HubRepositoryImpl.new);
() => HubRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
// UseCases // UseCases
i.addLazySingleton(GetHubsUseCase.new); i.addLazySingleton(GetHubsUseCase.new);
@@ -37,14 +31,7 @@ class ClientHubsModule extends Module {
i.addLazySingleton(AssignNfcTagUseCase.new); i.addLazySingleton(AssignNfcTagUseCase.new);
// BLoCs // BLoCs
i.add<ClientHubsBloc>( i.add<ClientHubsBloc>(ClientHubsBloc.new);
() => ClientHubsBloc(
getHubsUseCase: i.get<GetHubsUseCase>(),
createHubUseCase: i.get<CreateHubUseCase>(),
deleteHubUseCase: i.get<DeleteHubUseCase>(),
assignNfcTagUseCase: i.get<AssignNfcTagUseCase>(),
),
);
} }
@override @override

View File

@@ -9,30 +9,26 @@ import 'package:krow_domain/krow_domain.dart' as domain;
import 'package:krow_domain/krow_domain.dart' import 'package:krow_domain/krow_domain.dart'
show show
HubHasOrdersException, HubHasOrdersException,
HubCreationFailedException,
BusinessNotFoundException, BusinessNotFoundException,
NotAuthenticatedException; NotAuthenticatedException;
import '../../domain/repositories/hub_repository_interface.dart'; import '../../domain/repositories/hub_repository_interface.dart';
/// Implementation of [HubRepositoryInterface] backed by Data Connect. /// Implementation of [HubRepositoryInterface] backed by Data Connect.
class HubRepositoryImpl class HubRepositoryImpl implements HubRepositoryInterface {
with dc.DataErrorHandler
implements HubRepositoryInterface {
HubRepositoryImpl({ HubRepositoryImpl({
required firebase.FirebaseAuth firebaseAuth, required dc.DataConnectService service,
required dc.ExampleConnector dataConnect, }) : _service = service;
}) : _firebaseAuth = firebaseAuth,
_dataConnect = dataConnect;
final firebase.FirebaseAuth _firebaseAuth; final dc.DataConnectService _service;
final dc.ExampleConnector _dataConnect;
@override @override
Future<List<domain.Hub>> getHubs() async { Future<List<domain.Hub>> getHubs() async {
return _service.run(() async {
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser(); final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
final String teamId = await _getOrCreateTeamId(business); final String teamId = await _getOrCreateTeamId(business);
return _fetchHubsForTeam(teamId: teamId, businessId: business.id); return _fetchHubsForTeam(teamId: teamId, businessId: business.id);
});
} }
@override @override
@@ -48,6 +44,7 @@ class HubRepositoryImpl
String? country, String? country,
String? zipCode, String? zipCode,
}) async { }) async {
return _service.run(() async {
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser(); final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser();
final String teamId = await _getOrCreateTeamId(business); final String teamId = await _getOrCreateTeamId(business);
final _PlaceAddress? placeAddress = final _PlaceAddress? placeAddress =
@@ -59,7 +56,7 @@ class HubRepositoryImpl
final String? zipCodeValue = zipCode ?? placeAddress?.zipCode; final String? zipCodeValue = zipCode ?? placeAddress?.zipCode;
final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables>
result = await executeProtected(() => _dataConnect result = await _service.connector
.createTeamHub( .createTeamHub(
teamId: teamId, teamId: teamId,
hubName: name, hubName: name,
@@ -73,7 +70,7 @@ class HubRepositoryImpl
.street(streetValue) .street(streetValue)
.country(countryValue) .country(countryValue)
.zipCode(zipCodeValue) .zipCode(zipCodeValue)
.execute()); .execute();
final String createdId = result.data.teamHub_insert.id; final String createdId = result.data.teamHub_insert.id;
final List<domain.Hub> hubs = await _fetchHubsForTeam( final List<domain.Hub> hubs = await _fetchHubsForTeam(
@@ -96,26 +93,22 @@ class HubRepositoryImpl
nfcTagId: null, nfcTagId: null,
status: domain.HubStatus.active, status: domain.HubStatus.active,
); );
});
} }
@override @override
Future<void> deleteHub(String id) async { Future<void> deleteHub(String id) async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; return _service.run(() async {
if (businessId == null || businessId.isEmpty) { final String businessId = await _service.getBusinessId();
await _firebaseAuth.signOut();
throw const BusinessNotFoundException(
technicalMessage: 'Business ID missing from session',
);
}
final QueryResult<dc.ListOrdersByBusinessAndTeamHubData, final QueryResult<dc.ListOrdersByBusinessAndTeamHubData,
dc.ListOrdersByBusinessAndTeamHubVariables> result = dc.ListOrdersByBusinessAndTeamHubVariables> result =
await executeProtected(() => _dataConnect await _service.connector
.listOrdersByBusinessAndTeamHub( .listOrdersByBusinessAndTeamHub(
businessId: businessId, businessId: businessId,
teamHubId: id, teamHubId: id,
) )
.execute()); .execute();
if (result.data.orders.isNotEmpty) { if (result.data.orders.isNotEmpty) {
throw HubHasOrdersException( throw HubHasOrdersException(
@@ -123,7 +116,8 @@ class HubRepositoryImpl
); );
} }
await executeProtected(() => _dataConnect.deleteTeamHub(id: id).execute()); await _service.connector.deleteTeamHub(id: id).execute();
});
} }
@override @override
@@ -141,7 +135,7 @@ class HubRepositoryImpl
return dc.GetBusinessesByUserIdBusinesses( return dc.GetBusinessesByUserIdBusinesses(
id: cachedBusiness.id, id: cachedBusiness.id,
businessName: cachedBusiness.businessName, businessName: cachedBusiness.businessName,
userId: _firebaseAuth.currentUser?.uid ?? '', userId: _service.auth.currentUser?.uid ?? '',
rateGroup: const dc.Known<dc.BusinessRateGroup>(dc.BusinessRateGroup.STANDARD), rateGroup: const dc.Known<dc.BusinessRateGroup>(dc.BusinessRateGroup.STANDARD),
status: const dc.Known<dc.BusinessStatus>(dc.BusinessStatus.ACTIVE), status: const dc.Known<dc.BusinessStatus>(dc.BusinessStatus.ACTIVE),
contactName: cachedBusiness.contactName, contactName: cachedBusiness.contactName,
@@ -159,7 +153,7 @@ class HubRepositoryImpl
); );
} }
final firebase.User? user = _firebaseAuth.currentUser; final firebase.User? user = _service.auth.currentUser;
if (user == null) { if (user == null) {
throw const NotAuthenticatedException( throw const NotAuthenticatedException(
technicalMessage: 'No Firebase user in currentUser', technicalMessage: 'No Firebase user in currentUser',
@@ -168,11 +162,11 @@ class HubRepositoryImpl
final QueryResult<dc.GetBusinessesByUserIdData, final QueryResult<dc.GetBusinessesByUserIdData,
dc.GetBusinessesByUserIdVariables> result = dc.GetBusinessesByUserIdVariables> result =
await executeProtected(() => _dataConnect.getBusinessesByUserId( await _service.connector.getBusinessesByUserId(
userId: user.uid, userId: user.uid,
).execute()); ).execute();
if (result.data.businesses.isEmpty) { if (result.data.businesses.isEmpty) {
await _firebaseAuth.signOut(); await _service.auth.signOut();
throw BusinessNotFoundException( throw BusinessNotFoundException(
technicalMessage: 'No business found for user ${user.uid}', technicalMessage: 'No business found for user ${user.uid}',
); );
@@ -203,14 +197,14 @@ class HubRepositoryImpl
dc.GetBusinessesByUserIdBusinesses business, dc.GetBusinessesByUserIdBusinesses business,
) async { ) async {
final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables> final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables>
teamsResult = await executeProtected(() => _dataConnect.getTeamsByOwnerId( teamsResult = await _service.connector.getTeamsByOwnerId(
ownerId: business.id, ownerId: business.id,
).execute()); ).execute();
if (teamsResult.data.teams.isNotEmpty) { if (teamsResult.data.teams.isNotEmpty) {
return teamsResult.data.teams.first.id; return teamsResult.data.teams.first.id;
} }
final dc.CreateTeamVariablesBuilder createTeamBuilder = _dataConnect.createTeam( final dc.CreateTeamVariablesBuilder createTeamBuilder = _service.connector.createTeam(
teamName: '${business.businessName} Team', teamName: '${business.businessName} Team',
ownerId: business.id, ownerId: business.id,
ownerName: business.contactName ?? '', ownerName: business.contactName ?? '',
@@ -222,7 +216,7 @@ class HubRepositoryImpl
final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables> final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables>
createTeamResult = createTeamResult =
await executeProtected(() => createTeamBuilder.execute()); await createTeamBuilder.execute();
final String teamId = createTeamResult.data.team_insert.id; final String teamId = createTeamResult.data.team_insert.id;
return teamId; return teamId;
@@ -234,9 +228,9 @@ class HubRepositoryImpl
}) async { }) async {
final QueryResult<dc.GetTeamHubsByTeamIdData, final QueryResult<dc.GetTeamHubsByTeamIdData,
dc.GetTeamHubsByTeamIdVariables> hubsResult = dc.GetTeamHubsByTeamIdVariables> hubsResult =
await executeProtected(() => _dataConnect.getTeamHubsByTeamId( await _service.connector.getTeamHubsByTeamId(
teamId: teamId, teamId: teamId,
).execute()); ).execute();
return hubsResult.data.teamHubs return hubsResult.data.teamHubs
.map( .map(

View File

@@ -1,6 +1,6 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'src/data/repositories_impl/settings_repository_impl.dart'; import 'src/data/repositories_impl/settings_repository_impl.dart';
import 'src/domain/repositories/settings_repository_interface.dart'; import 'src/domain/repositories/settings_repository_interface.dart';
import 'src/domain/usecases/sign_out_usecase.dart'; import 'src/domain/usecases/sign_out_usecase.dart';
@@ -9,20 +9,19 @@ import 'src/presentation/pages/client_settings_page.dart';
/// A [Module] for the client settings feature. /// A [Module] for the client settings feature.
class ClientSettingsModule extends Module { class ClientSettingsModule extends Module {
@override
List<Module> get imports => <Module>[DataConnectModule()];
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.addLazySingleton<SettingsRepositoryInterface>( i.addLazySingleton<SettingsRepositoryInterface>(SettingsRepositoryImpl.new);
() => SettingsRepositoryImpl(firebaseAuth: FirebaseAuth.instance),
);
// UseCases // UseCases
i.addLazySingleton(SignOutUseCase.new); i.addLazySingleton(SignOutUseCase.new);
// BLoCs // BLoCs
i.add<ClientSettingsBloc>( i.add<ClientSettingsBloc>(ClientSettingsBloc.new);
() => ClientSettingsBloc(signOutUseCase: i.get<SignOutUseCase>()),
);
} }
@override @override

View File

@@ -1,23 +1,21 @@
import 'package:firebase_auth/firebase_auth.dart'; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import '../../domain/repositories/settings_repository_interface.dart'; import '../../domain/repositories/settings_repository_interface.dart';
/// Implementation of [SettingsRepositoryInterface]. /// Implementation of [SettingsRepositoryInterface].
/// ///
/// This implementation delegates authentication operations to [FirebaseAuth]. /// This implementation delegates authentication operations to [DataConnectService].
class SettingsRepositoryImpl implements SettingsRepositoryInterface { class SettingsRepositoryImpl implements SettingsRepositoryInterface {
/// Creates a [SettingsRepositoryImpl] with the required [_firebaseAuth]. /// Creates a [SettingsRepositoryImpl] with the required [_service].
const SettingsRepositoryImpl({required this.firebaseAuth}); const SettingsRepositoryImpl({required dc.DataConnectService service}) : _service = service;
/// The Firebase Auth instance. /// The Data Connect service.
final FirebaseAuth firebaseAuth; final dc.DataConnectService _service;
@override @override
Future<void> signOut() async { Future<void> signOut() async {
try { return _service.run(() async {
await firebaseAuth.signOut(); await _service.auth.signOut();
} catch (e) { });
throw Exception('Error signing out: ${e.toString()}');
}
} }
} }

View File

@@ -1,4 +1,3 @@
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -6,53 +5,42 @@ import 'package:krow_domain/krow_domain.dart' as domain;
import '../../domain/repositories/i_view_orders_repository.dart'; import '../../domain/repositories/i_view_orders_repository.dart';
/// Implementation of [IViewOrdersRepository] using Data Connect. /// Implementation of [IViewOrdersRepository] using Data Connect.
class ViewOrdersRepositoryImpl class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
with dc.DataErrorHandler final dc.DataConnectService _service;
implements IViewOrdersRepository {
final firebase.FirebaseAuth _firebaseAuth;
final dc.ExampleConnector _dataConnect;
ViewOrdersRepositoryImpl({ ViewOrdersRepositoryImpl({
required firebase.FirebaseAuth firebaseAuth, required dc.DataConnectService service,
required dc.ExampleConnector dataConnect, }) : _service = service;
}) : _firebaseAuth = firebaseAuth,
_dataConnect = dataConnect;
@override @override
Future<List<domain.OrderItem>> getOrdersForRange({ Future<List<domain.OrderItem>> getOrdersForRange({
required DateTime start, required DateTime start,
required DateTime end, required DateTime end,
}) async { }) async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; return _service.run(() async {
if (businessId == null || businessId.isEmpty) { final String businessId = await _service.getBusinessId();
await _firebaseAuth.signOut();
throw Exception('Business is missing. Please sign in again.');
}
final fdc.Timestamp startTimestamp = _toTimestamp(_startOfDay(start)); final fdc.Timestamp startTimestamp = _service.toTimestamp(_startOfDay(start));
final fdc.Timestamp endTimestamp = _toTimestamp(_endOfDay(end)); final fdc.Timestamp endTimestamp = _service.toTimestamp(_endOfDay(end));
final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData, final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData,
dc.ListShiftRolesByBusinessAndDateRangeVariables> result = dc.ListShiftRolesByBusinessAndDateRangeVariables> result =
await executeProtected(() => _dataConnect await _service.connector
.listShiftRolesByBusinessAndDateRange( .listShiftRolesByBusinessAndDateRange(
businessId: businessId, businessId: businessId,
start: startTimestamp, start: startTimestamp,
end: endTimestamp, end: endTimestamp,
) )
.execute()); .execute();
print( print(
'ViewOrders range start=${start.toIso8601String()} end=${end.toIso8601String()} shiftRoles=${result.data.shiftRoles.length}', 'ViewOrders range start=${start.toIso8601String()} end=${end.toIso8601String()} shiftRoles=${result.data.shiftRoles.length}',
); );
final String businessName = final String businessName =
dc.ClientSessionStore.instance.session?.business?.businessName ?? dc.ClientSessionStore.instance.session?.business?.businessName ?? 'Your Company';
'Your Company';
return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole) { return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole) {
final DateTime? shiftDate = shiftRole.shift.date?.toDateTime().toLocal(); final DateTime? shiftDate = shiftRole.shift.date?.toDateTime().toLocal();
final String dateStr = shiftDate == null final String dateStr = shiftDate == null ? '' : DateFormat('yyyy-MM-dd').format(shiftDate);
? ''
: DateFormat('yyyy-MM-dd').format(shiftDate);
final String startTime = _formatTime(shiftRole.startTime); final String startTime = _formatTime(shiftRole.startTime);
final String endTime = _formatTime(shiftRole.endTime); final String endTime = _formatTime(shiftRole.endTime);
final int filled = shiftRole.assigned ?? 0; final int filled = shiftRole.assigned ?? 0;
@@ -69,8 +57,7 @@ class ViewOrdersRepositoryImpl
'end=${shiftRole.endTime?.toJson()} hours=$hours totalValue=$totalValue', 'end=${shiftRole.endTime?.toJson()} hours=$hours totalValue=$totalValue',
); );
final String eventName = final String eventName = shiftRole.shift.order.eventName ?? shiftRole.shift.title;
shiftRole.shift.order.eventName ?? shiftRole.shift.title;
return domain.OrderItem( return domain.OrderItem(
id: _shiftRoleKey(shiftRole.shiftId, shiftRole.roleId), id: _shiftRoleKey(shiftRole.shiftId, shiftRole.roleId),
@@ -91,36 +78,35 @@ class ViewOrdersRepositoryImpl
confirmedApps: const <Map<String, dynamic>>[], confirmedApps: const <Map<String, dynamic>>[],
); );
}).toList(); }).toList();
});
} }
@override @override
Future<Map<String, List<Map<String, dynamic>>>> getAcceptedApplicationsForDay( Future<Map<String, List<Map<String, dynamic>>>> getAcceptedApplicationsForDay(
DateTime day, DateTime day,
) async { ) async {
final String? businessId = dc.ClientSessionStore.instance.session?.business?.id; return _service.run(() async {
if (businessId == null || businessId.isEmpty) { final String businessId = await _service.getBusinessId();
await _firebaseAuth.signOut();
throw Exception('Business is missing. Please sign in again.');
}
final fdc.Timestamp dayStart = _toTimestamp(_startOfDay(day)); final fdc.Timestamp dayStart = _service.toTimestamp(_startOfDay(day));
final fdc.Timestamp dayEnd = _toTimestamp(_endOfDay(day)); final fdc.Timestamp dayEnd = _service.toTimestamp(_endOfDay(day));
final fdc.QueryResult<dc.ListAcceptedApplicationsByBusinessForDayData, final fdc.QueryResult<dc.ListAcceptedApplicationsByBusinessForDayData,
dc.ListAcceptedApplicationsByBusinessForDayVariables> result = dc.ListAcceptedApplicationsByBusinessForDayVariables> result =
await executeProtected(() => _dataConnect await _service.connector
.listAcceptedApplicationsByBusinessForDay( .listAcceptedApplicationsByBusinessForDay(
businessId: businessId, businessId: businessId,
dayStart: dayStart, dayStart: dayStart,
dayEnd: dayEnd, dayEnd: dayEnd,
) )
.execute()); .execute();
print( print(
'ViewOrders day=${day.toIso8601String()} applications=${result.data.applications.length}', 'ViewOrders day=${day.toIso8601String()} applications=${result.data.applications.length}',
); );
final Map<String, List<Map<String, dynamic>>> grouped = <String, List<Map<String, dynamic>>>{}; final Map<String, List<Map<String, dynamic>>> grouped = <String, List<Map<String, dynamic>>>{};
for (final dc.ListAcceptedApplicationsByBusinessForDayApplications application in result.data.applications) { for (final dc.ListAcceptedApplicationsByBusinessForDayApplications application
in result.data.applications) {
print( print(
'ViewOrders app: shiftId=${application.shiftId} roleId=${application.roleId} ' 'ViewOrders app: shiftId=${application.shiftId} roleId=${application.roleId} '
'checkIn=${application.checkInTime?.toJson()} checkOut=${application.checkOutTime?.toJson()}', 'checkIn=${application.checkInTime?.toJson()} checkOut=${application.checkOutTime?.toJson()}',
@@ -138,19 +124,13 @@ class ViewOrdersRepositoryImpl
}); });
} }
return grouped; return grouped;
});
} }
String _shiftRoleKey(String shiftId, String roleId) { String _shiftRoleKey(String shiftId, String roleId) {
return '$shiftId:$roleId'; return '$shiftId:$roleId';
} }
fdc.Timestamp _toTimestamp(DateTime dateTime) {
final DateTime utc = dateTime.toUtc();
final int seconds = utc.millisecondsSinceEpoch ~/ 1000;
final int nanoseconds = (utc.microsecondsSinceEpoch % 1000000) * 1000;
return fdc.Timestamp(nanoseconds, seconds);
}
DateTime _startOfDay(DateTime dateTime) { DateTime _startOfDay(DateTime dateTime) {
return DateTime(dateTime.year, dateTime.month, dateTime.day); return DateTime(dateTime.year, dateTime.month, dateTime.day);
} }

View File

@@ -80,23 +80,6 @@ class _ViewOrderCardState extends State<ViewOrderCard> {
} }
} }
/// Formats the date string for display.
String _formatDate({required String dateStr}) {
try {
final DateTime date = DateTime.parse(dateStr);
final DateTime now = DateTime.now();
final DateTime today = DateTime(now.year, now.month, now.day);
final DateTime tomorrow = today.add(const Duration(days: 1));
final DateTime checkDate = DateTime(date.year, date.month, date.day);
if (checkDate == today) return t.client_view_orders.card.today;
if (checkDate == tomorrow) return t.client_view_orders.card.tomorrow;
return DateFormat('EEE, MMM d').format(date);
} catch (_) {
return dateStr;
}
}
/// Formats the time string for display. /// Formats the time string for display.
String _formatTime({required String timeStr}) { String _formatTime({required String timeStr}) {
if (timeStr.isEmpty) return ''; if (timeStr.isEmpty) return '';
@@ -829,10 +812,7 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
final String dateText = orderDate == null final String dateText = orderDate == null
? widget.order.date ? widget.order.date
: DateFormat('yyyy-MM-dd').format(orderDate); : DateFormat('yyyy-MM-dd').format(orderDate);
final String location = firstShift.order.teamHub?.hubName ?? final String location = firstShift.order.teamHub.hubName;
firstShift.locationAddress ??
firstShift.location ??
widget.order.locationAddress;
_dateController.text = dateText; _dateController.text = dateText;
_globalLocationController.text = location; _globalLocationController.text = location;

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'data/repositories/view_orders_repository_impl.dart'; import 'data/repositories/view_orders_repository_impl.dart';
import 'domain/repositories/i_view_orders_repository.dart'; import 'domain/repositories/i_view_orders_repository.dart';
@@ -21,24 +20,14 @@ class ViewOrdersModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Repositories // Repositories
i.add<IViewOrdersRepository>( i.add<IViewOrdersRepository>(ViewOrdersRepositoryImpl.new);
() => ViewOrdersRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
// UseCases // UseCases
i.add(GetOrdersUseCase.new); i.add(GetOrdersUseCase.new);
i.add(GetAcceptedApplicationsForDayUseCase.new); i.add(GetAcceptedApplicationsForDayUseCase.new);
// BLoCs // BLoCs
i.add( i.add(ViewOrdersCubit.new);
() => ViewOrdersCubit(
getOrdersUseCase: i.get<GetOrdersUseCase>(),
getAcceptedAppsUseCase: i.get<GetAcceptedApplicationsForDayUseCase>(),
),
);
} }
@override @override