fix: add ignore_for_file to data connect Repos and modify CI to avoid analyzing deleted files
This commit is contained in:
@@ -55,7 +55,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final User user = await _signInWithEmail(
|
||||
SignInWithEmailArguments(email: event.email, password: event.password),
|
||||
@@ -77,7 +77,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final User user = await _signUpWithEmail(
|
||||
SignUpWithEmailArguments(
|
||||
@@ -103,7 +103,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final User user = await _signInWithSocial(
|
||||
SignInWithSocialArguments(provider: event.provider),
|
||||
@@ -125,7 +125,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
|
||||
emit(state.copyWith(status: ClientAuthStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _signOut();
|
||||
emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/billing_repository.dart';
|
||||
@@ -7,8 +8,6 @@ import '../../domain/repositories/billing_repository.dart';
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
class BillingRepositoryImpl implements BillingRepository {
|
||||
final dc.BillingConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
BillingRepositoryImpl({
|
||||
dc.BillingConnectorRepository? connectorRepository,
|
||||
@@ -16,28 +15,30 @@ class BillingRepositoryImpl implements BillingRepository {
|
||||
}) : _connectorRepository = connectorRepository ??
|
||||
dc.DataConnectService.instance.getBillingRepository(),
|
||||
_service = service ?? dc.DataConnectService.instance;
|
||||
final dc.BillingConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<List<BusinessBankAccount>> getBankAccounts() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getBankAccounts(businessId: businessId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<double> getCurrentBillAmount() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getCurrentBillAmount(businessId: businessId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Invoice>> getInvoiceHistory() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getInvoiceHistory(businessId: businessId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Invoice>> getPendingInvoices() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getPendingInvoices(businessId: businessId);
|
||||
}
|
||||
|
||||
@@ -49,10 +50,11 @@ class BillingRepositoryImpl implements BillingRepository {
|
||||
|
||||
@override
|
||||
Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getSpendingBreakdown(
|
||||
businessId: businessId,
|
||||
period: period,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
||||
) async {
|
||||
emit(state.copyWith(status: BillingStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final List<dynamic> results =
|
||||
await Future.wait<dynamic>(<Future<dynamic>>[
|
||||
@@ -102,7 +102,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
|
||||
Emitter<BillingState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final List<InvoiceItem> spendingItems =
|
||||
await _getSpendingBreakdown.call(event.period);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/coverage_repository.dart';
|
||||
@@ -7,8 +8,6 @@ import '../../domain/repositories/coverage_repository.dart';
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
class CoverageRepositoryImpl implements CoverageRepository {
|
||||
final dc.CoverageConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
CoverageRepositoryImpl({
|
||||
dc.CoverageConnectorRepository? connectorRepository,
|
||||
@@ -16,10 +15,12 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
}) : _connectorRepository = connectorRepository ??
|
||||
dc.DataConnectService.instance.getCoverageRepository(),
|
||||
_service = service ?? dc.DataConnectService.instance;
|
||||
final dc.CoverageConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getShiftsForDate(
|
||||
businessId: businessId,
|
||||
date: date,
|
||||
@@ -58,3 +59,4 @@ class CoverageRepositoryImpl implements CoverageRepository {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
);
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
// Fetch shifts and stats concurrently
|
||||
final List<Object> results = await Future.wait<Object>(<Future<Object>>[
|
||||
|
||||
@@ -36,7 +36,7 @@ class ClientMainBottomBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = Translations.of(context);
|
||||
final Translations t = Translations.of(context);
|
||||
// Client App colors from design system
|
||||
const Color activeColor = UiColors.textPrimary;
|
||||
const Color inactiveColor = UiColors.textInactive;
|
||||
|
||||
@@ -20,7 +20,7 @@ class ClientCreateOrderBloc
|
||||
Emitter<ClientCreateOrderState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final List<OrderType> types = await _getOrderTypesUseCase();
|
||||
emit(ClientCreateOrderLoadSuccess(types));
|
||||
|
||||
@@ -220,7 +220,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
|
||||
) async {
|
||||
emit(state.copyWith(status: OneTimeOrderStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final OneTimeOrderRoleOption role in state.roles)
|
||||
|
||||
@@ -272,7 +272,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
) async {
|
||||
emit(state.copyWith(status: PermanentOrderStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final PermanentOrderRoleOption role in state.roles)
|
||||
@@ -280,7 +280,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
|
||||
};
|
||||
final PermanentOrderHubOption? selectedHub = state.selectedHub;
|
||||
if (selectedHub == null) {
|
||||
throw domain.OrderMissingHubException();
|
||||
throw const domain.OrderMissingHubException();
|
||||
}
|
||||
final domain.PermanentOrder order = domain.PermanentOrder(
|
||||
startDate: state.startDate,
|
||||
|
||||
@@ -69,7 +69,7 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
|
||||
emit(const RapidOrderSubmitting());
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _createRapidOrderUseCase(
|
||||
RapidOrderArguments(description: message),
|
||||
|
||||
@@ -289,7 +289,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
) async {
|
||||
emit(state.copyWith(status: RecurringOrderStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final Map<String, double> roleRates = <String, double>{
|
||||
for (final RecurringOrderRoleOption role in state.roles)
|
||||
@@ -297,7 +297,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
|
||||
};
|
||||
final RecurringOrderHubOption? selectedHub = state.selectedHub;
|
||||
if (selectedHub == null) {
|
||||
throw domain.OrderMissingHubException();
|
||||
throw const domain.OrderMissingHubException();
|
||||
}
|
||||
final domain.RecurringOrder order = domain.RecurringOrder(
|
||||
startDate: state.startDate,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:firebase_data_connect/src/core/ref.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/home_repository_interface.dart';
|
||||
@@ -7,8 +9,6 @@ import '../../domain/repositories/home_repository_interface.dart';
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
class HomeRepositoryImpl implements HomeRepositoryInterface {
|
||||
final dc.HomeConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
HomeRepositoryImpl({
|
||||
dc.HomeConnectorRepository? connectorRepository,
|
||||
@@ -16,10 +16,12 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
|
||||
}) : _connectorRepository = connectorRepository ??
|
||||
dc.DataConnectService.instance.getHomeRepository(),
|
||||
_service = service ?? dc.DataConnectService.instance;
|
||||
final dc.HomeConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<HomeDashboardData> getDashboardData() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getDashboardData(businessId: businessId);
|
||||
}
|
||||
|
||||
@@ -37,16 +39,16 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
|
||||
|
||||
return await _service.run(() async {
|
||||
final String businessId = await _service.getBusinessId();
|
||||
final businessResult = await _service.connector
|
||||
final QueryResult<dc.GetBusinessByIdData, dc.GetBusinessByIdVariables> businessResult = await _service.connector
|
||||
.getBusinessById(id: businessId)
|
||||
.execute();
|
||||
|
||||
final b = businessResult.data.business;
|
||||
final dc.GetBusinessByIdBusiness? b = businessResult.data.business;
|
||||
if (b == null) {
|
||||
throw Exception('Business data not found for ID: $businessId');
|
||||
}
|
||||
|
||||
final updatedSession = dc.ClientSession(
|
||||
final dc.ClientSession updatedSession = dc.ClientSession(
|
||||
business: dc.ClientBusinessSession(
|
||||
id: b.id,
|
||||
businessName: b.businessName,
|
||||
@@ -67,7 +69,8 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
|
||||
|
||||
@override
|
||||
Future<List<ReorderItem>> getRecentReorders() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getRecentReorders(businessId: businessId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState>
|
||||
) async {
|
||||
emit(state.copyWith(status: ClientHomeStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
// Get session data
|
||||
final UserSessionData sessionData = await _getUserSessionDataUseCase();
|
||||
|
||||
@@ -651,9 +651,9 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
|
||||
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.95,
|
||||
decoration: BoxDecoration(
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.bgPrimary,
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
|
||||
),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/hub_repository_interface.dart';
|
||||
@@ -7,8 +8,6 @@ import '../../domain/repositories/hub_repository_interface.dart';
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
final dc.HubsConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
HubRepositoryImpl({
|
||||
dc.HubsConnectorRepository? connectorRepository,
|
||||
@@ -16,10 +15,12 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
}) : _connectorRepository = connectorRepository ??
|
||||
dc.DataConnectService.instance.getHubsRepository(),
|
||||
_service = service ?? dc.DataConnectService.instance;
|
||||
final dc.HubsConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<List<Hub>> getHubs() async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getHubs(businessId: businessId);
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
String? country,
|
||||
String? zipCode,
|
||||
}) async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.createHub(
|
||||
businessId: businessId,
|
||||
name: name,
|
||||
@@ -54,7 +55,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
|
||||
@override
|
||||
Future<void> deleteHub(String id) async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.deleteHub(businessId: businessId, id: id);
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
String? country,
|
||||
String? zipCode,
|
||||
}) async {
|
||||
final businessId = await _service.getBusinessId();
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.updateHub(
|
||||
businessId: businessId,
|
||||
id: id,
|
||||
@@ -96,3 +97,4 @@ class HubRepositoryImpl implements HubRepositoryInterface {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
|
||||
@@ -92,7 +92,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _createHubUseCase(
|
||||
CreateHubArguments(
|
||||
@@ -132,7 +132,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _updateHubUseCase(
|
||||
UpdateHubArguments(
|
||||
@@ -172,7 +172,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId));
|
||||
final List<Hub> hubs = await _getHubsUseCase();
|
||||
@@ -198,7 +198,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
|
||||
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _assignNfcTagUseCase(
|
||||
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
|
||||
|
||||
@@ -7,10 +7,10 @@ import '../../domain/repositories/reports_repository.dart';
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
class ReportsRepositoryImpl implements ReportsRepository {
|
||||
final ReportsConnectorRepository _connectorRepository;
|
||||
|
||||
ReportsRepositoryImpl({ReportsConnectorRepository? connectorRepository})
|
||||
: _connectorRepository = connectorRepository ?? DataConnectService.instance.getReportsRepository();
|
||||
final ReportsConnectorRepository _connectorRepository;
|
||||
|
||||
@override
|
||||
Future<DailyOpsReport> getDailyOpsReport({
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/coverage_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'coverage_event.dart';
|
||||
import 'coverage_state.dart';
|
||||
|
||||
class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
CoverageBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(CoverageInitial()) {
|
||||
on<LoadCoverageReport>(_onLoadCoverageReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadCoverageReport(
|
||||
LoadCoverageReport event,
|
||||
@@ -18,7 +20,7 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
) async {
|
||||
emit(CoverageLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getCoverageReport(
|
||||
final CoverageReport report = await _reportsRepository.getCoverageReport(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
@@ -29,3 +31,4 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class CoverageEvent extends Equatable {
|
||||
const CoverageEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadCoverageReport extends CoverageEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadCoverageReport({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class CoverageState extends Equatable {
|
||||
const CoverageState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class CoverageInitial extends CoverageState {}
|
||||
@@ -13,19 +14,20 @@ class CoverageInitial extends CoverageState {}
|
||||
class CoverageLoading extends CoverageState {}
|
||||
|
||||
class CoverageLoaded extends CoverageState {
|
||||
final CoverageReport report;
|
||||
|
||||
const CoverageLoaded(this.report);
|
||||
final CoverageReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class CoverageError extends CoverageState {
|
||||
final String message;
|
||||
|
||||
const CoverageError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/daily_ops_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'daily_ops_event.dart';
|
||||
import 'daily_ops_state.dart';
|
||||
|
||||
class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
DailyOpsBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(DailyOpsInitial()) {
|
||||
on<LoadDailyOpsReport>(_onLoadDailyOpsReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadDailyOpsReport(
|
||||
LoadDailyOpsReport event,
|
||||
@@ -18,7 +19,7 @@ class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState> {
|
||||
) async {
|
||||
emit(DailyOpsLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getDailyOpsReport(
|
||||
final DailyOpsReport report = await _reportsRepository.getDailyOpsReport(
|
||||
businessId: event.businessId,
|
||||
date: event.date,
|
||||
);
|
||||
|
||||
@@ -4,18 +4,18 @@ abstract class DailyOpsEvent extends Equatable {
|
||||
const DailyOpsEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadDailyOpsReport extends DailyOpsEvent {
|
||||
final String? businessId;
|
||||
final DateTime date;
|
||||
|
||||
const LoadDailyOpsReport({
|
||||
this.businessId,
|
||||
required this.date,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime date;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, date];
|
||||
List<Object?> get props => <Object?>[businessId, date];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class DailyOpsState extends Equatable {
|
||||
const DailyOpsState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class DailyOpsInitial extends DailyOpsState {}
|
||||
@@ -13,19 +14,20 @@ class DailyOpsInitial extends DailyOpsState {}
|
||||
class DailyOpsLoading extends DailyOpsState {}
|
||||
|
||||
class DailyOpsLoaded extends DailyOpsState {
|
||||
final DailyOpsReport report;
|
||||
|
||||
const DailyOpsLoaded(this.report);
|
||||
final DailyOpsReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class DailyOpsError extends DailyOpsState {
|
||||
final String message;
|
||||
|
||||
const DailyOpsError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/forecast_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'forecast_event.dart';
|
||||
import 'forecast_state.dart';
|
||||
|
||||
class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
ForecastBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(ForecastInitial()) {
|
||||
on<LoadForecastReport>(_onLoadForecastReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadForecastReport(
|
||||
LoadForecastReport event,
|
||||
@@ -18,7 +19,7 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
|
||||
) async {
|
||||
emit(ForecastLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getForecastReport(
|
||||
final ForecastReport report = await _reportsRepository.getForecastReport(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
|
||||
@@ -4,20 +4,20 @@ abstract class ForecastEvent extends Equatable {
|
||||
const ForecastEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadForecastReport extends ForecastEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadForecastReport({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class ForecastState extends Equatable {
|
||||
const ForecastState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class ForecastInitial extends ForecastState {}
|
||||
@@ -13,19 +14,20 @@ class ForecastInitial extends ForecastState {}
|
||||
class ForecastLoading extends ForecastState {}
|
||||
|
||||
class ForecastLoaded extends ForecastState {
|
||||
final ForecastReport report;
|
||||
|
||||
const ForecastLoaded(this.report);
|
||||
final ForecastReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class ForecastError extends ForecastState {
|
||||
final String message;
|
||||
|
||||
const ForecastError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/no_show_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'no_show_event.dart';
|
||||
import 'no_show_state.dart';
|
||||
|
||||
class NoShowBloc extends Bloc<NoShowEvent, NoShowState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
NoShowBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(NoShowInitial()) {
|
||||
on<LoadNoShowReport>(_onLoadNoShowReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadNoShowReport(
|
||||
LoadNoShowReport event,
|
||||
@@ -18,7 +19,7 @@ class NoShowBloc extends Bloc<NoShowEvent, NoShowState> {
|
||||
) async {
|
||||
emit(NoShowLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getNoShowReport(
|
||||
final NoShowReport report = await _reportsRepository.getNoShowReport(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
|
||||
@@ -4,20 +4,20 @@ abstract class NoShowEvent extends Equatable {
|
||||
const NoShowEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadNoShowReport extends NoShowEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadNoShowReport({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class NoShowState extends Equatable {
|
||||
const NoShowState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class NoShowInitial extends NoShowState {}
|
||||
@@ -13,19 +14,20 @@ class NoShowInitial extends NoShowState {}
|
||||
class NoShowLoading extends NoShowState {}
|
||||
|
||||
class NoShowLoaded extends NoShowState {
|
||||
final NoShowReport report;
|
||||
|
||||
const NoShowLoaded(this.report);
|
||||
final NoShowReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class NoShowError extends NoShowState {
|
||||
final String message;
|
||||
|
||||
const NoShowError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/performance_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'performance_event.dart';
|
||||
import 'performance_state.dart';
|
||||
|
||||
class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
PerformanceBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(PerformanceInitial()) {
|
||||
on<LoadPerformanceReport>(_onLoadPerformanceReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadPerformanceReport(
|
||||
LoadPerformanceReport event,
|
||||
@@ -18,7 +19,7 @@ class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState> {
|
||||
) async {
|
||||
emit(PerformanceLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getPerformanceReport(
|
||||
final PerformanceReport report = await _reportsRepository.getPerformanceReport(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
|
||||
@@ -4,20 +4,20 @@ abstract class PerformanceEvent extends Equatable {
|
||||
const PerformanceEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadPerformanceReport extends PerformanceEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadPerformanceReport({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class PerformanceState extends Equatable {
|
||||
const PerformanceState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class PerformanceInitial extends PerformanceState {}
|
||||
@@ -13,19 +14,20 @@ class PerformanceInitial extends PerformanceState {}
|
||||
class PerformanceLoading extends PerformanceState {}
|
||||
|
||||
class PerformanceLoaded extends PerformanceState {
|
||||
final PerformanceReport report;
|
||||
|
||||
const PerformanceLoaded(this.report);
|
||||
final PerformanceReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class PerformanceError extends PerformanceState {
|
||||
final String message;
|
||||
|
||||
const PerformanceError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/spend_report.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'spend_event.dart';
|
||||
import 'spend_state.dart';
|
||||
|
||||
class SpendBloc extends Bloc<SpendEvent, SpendState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
SpendBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(SpendInitial()) {
|
||||
on<LoadSpendReport>(_onLoadSpendReport);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadSpendReport(
|
||||
LoadSpendReport event,
|
||||
@@ -18,7 +19,7 @@ class SpendBloc extends Bloc<SpendEvent, SpendState> {
|
||||
) async {
|
||||
emit(SpendLoading());
|
||||
try {
|
||||
final report = await _reportsRepository.getSpendReport(
|
||||
final SpendReport report = await _reportsRepository.getSpendReport(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
|
||||
@@ -4,20 +4,20 @@ abstract class SpendEvent extends Equatable {
|
||||
const SpendEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadSpendReport extends SpendEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadSpendReport({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class SpendState extends Equatable {
|
||||
const SpendState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class SpendInitial extends SpendState {}
|
||||
@@ -13,19 +14,20 @@ class SpendInitial extends SpendState {}
|
||||
class SpendLoading extends SpendState {}
|
||||
|
||||
class SpendLoaded extends SpendState {
|
||||
final SpendReport report;
|
||||
|
||||
const SpendLoaded(this.report);
|
||||
final SpendReport report;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [report];
|
||||
List<Object?> get props => <Object?>[report];
|
||||
}
|
||||
|
||||
class SpendError extends SpendState {
|
||||
final String message;
|
||||
|
||||
const SpendError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/src/entities/reports/reports_summary.dart';
|
||||
import '../../../domain/repositories/reports_repository.dart';
|
||||
import 'reports_summary_event.dart';
|
||||
import 'reports_summary_state.dart';
|
||||
|
||||
class ReportsSummaryBloc extends Bloc<ReportsSummaryEvent, ReportsSummaryState> {
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
ReportsSummaryBloc({required ReportsRepository reportsRepository})
|
||||
: _reportsRepository = reportsRepository,
|
||||
super(ReportsSummaryInitial()) {
|
||||
on<LoadReportsSummary>(_onLoadReportsSummary);
|
||||
}
|
||||
final ReportsRepository _reportsRepository;
|
||||
|
||||
Future<void> _onLoadReportsSummary(
|
||||
LoadReportsSummary event,
|
||||
@@ -18,7 +19,7 @@ class ReportsSummaryBloc extends Bloc<ReportsSummaryEvent, ReportsSummaryState>
|
||||
) async {
|
||||
emit(ReportsSummaryLoading());
|
||||
try {
|
||||
final summary = await _reportsRepository.getReportsSummary(
|
||||
final ReportsSummary summary = await _reportsRepository.getReportsSummary(
|
||||
businessId: event.businessId,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
|
||||
@@ -4,20 +4,20 @@ abstract class ReportsSummaryEvent extends Equatable {
|
||||
const ReportsSummaryEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class LoadReportsSummary extends ReportsSummaryEvent {
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
const LoadReportsSummary({
|
||||
this.businessId,
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
});
|
||||
final String? businessId;
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [businessId, startDate, endDate];
|
||||
List<Object?> get props => <Object?>[businessId, startDate, endDate];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -5,7 +6,7 @@ abstract class ReportsSummaryState extends Equatable {
|
||||
const ReportsSummaryState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
List<Object?> get props => <Object?>[];
|
||||
}
|
||||
|
||||
class ReportsSummaryInitial extends ReportsSummaryState {}
|
||||
@@ -13,19 +14,20 @@ class ReportsSummaryInitial extends ReportsSummaryState {}
|
||||
class ReportsSummaryLoading extends ReportsSummaryState {}
|
||||
|
||||
class ReportsSummaryLoaded extends ReportsSummaryState {
|
||||
final ReportsSummary summary;
|
||||
|
||||
const ReportsSummaryLoaded(this.summary);
|
||||
final ReportsSummary summary;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [summary];
|
||||
List<Object?> get props => <Object?>[summary];
|
||||
}
|
||||
|
||||
class ReportsSummaryError extends ReportsSummaryState {
|
||||
final String message;
|
||||
|
||||
const ReportsSummaryError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
List<Object?> get props => <Object?>[message];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart';
|
||||
@@ -23,12 +24,12 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<CoverageBloc>()
|
||||
create: (BuildContext context) => Modular.get<CoverageBloc>()
|
||||
..add(LoadCoverageReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<CoverageBloc, CoverageState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, CoverageState state) {
|
||||
if (state is CoverageLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -38,10 +39,10 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
}
|
||||
|
||||
if (state is CoverageLoaded) {
|
||||
final report = state.report;
|
||||
final CoverageReport report = state.report;
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -52,16 +53,16 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [UiColors.primary, UiColors.tagInProgress],
|
||||
colors: <Color>[UiColors.primary, UiColors.tagInProgress],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -81,7 +82,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.coverage_report.title,
|
||||
style: const TextStyle(
|
||||
@@ -113,10 +114,10 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Summary Cards
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _CoverageSummaryCard(
|
||||
label: context.t.client_reports.coverage_report.metrics.avg_coverage,
|
||||
@@ -152,7 +153,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
if (report.dailyCoverage.isEmpty)
|
||||
Center(child: Text(context.t.client_reports.coverage_report.empty_state))
|
||||
else
|
||||
...report.dailyCoverage.map((day) => _CoverageListItem(
|
||||
...report.dailyCoverage.map((CoverageDay day) => _CoverageListItem(
|
||||
date: DateFormat('EEE, MMM dd').format(day.date),
|
||||
needed: day.needed,
|
||||
filled: day.filled,
|
||||
@@ -176,10 +177,6 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
}
|
||||
|
||||
class _CoverageSummaryCard extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
|
||||
const _CoverageSummaryCard({
|
||||
required this.label,
|
||||
@@ -187,6 +184,10 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
required this.icon,
|
||||
required this.color,
|
||||
});
|
||||
final String label;
|
||||
final String value;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -195,7 +196,7 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -204,7 +205,7 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
@@ -224,10 +225,6 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CoverageListItem extends StatelessWidget {
|
||||
final String date;
|
||||
final int needed;
|
||||
final int filled;
|
||||
final double percentage;
|
||||
|
||||
const _CoverageListItem({
|
||||
required this.date,
|
||||
@@ -235,6 +232,10 @@ class _CoverageListItem extends StatelessWidget {
|
||||
required this.filled,
|
||||
required this.percentage,
|
||||
});
|
||||
final String date;
|
||||
final int needed;
|
||||
final int filled;
|
||||
final double percentage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -255,11 +256,11 @@ class _CoverageListItem extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(date, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
// Progress Bar
|
||||
@@ -278,7 +279,7 @@ class _CoverageListItem extends StatelessWidget {
|
||||
const SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'$filled/$needed',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
@@ -298,3 +299,4 @@ class _CoverageListItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/src/entities/reports/daily_ops_report.dart';
|
||||
|
||||
class DailyOpsReportPage extends StatefulWidget {
|
||||
const DailyOpsReportPage({super.key});
|
||||
@@ -49,12 +50,12 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<DailyOpsBloc>()
|
||||
create: (BuildContext context) => Modular.get<DailyOpsBloc>()
|
||||
..add(LoadDailyOpsReport(date: _selectedDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<DailyOpsBloc, DailyOpsState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, DailyOpsState state) {
|
||||
if (state is DailyOpsLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -64,10 +65,10 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
}
|
||||
|
||||
if (state is DailyOpsLoaded) {
|
||||
final report = state.report;
|
||||
final DailyOpsReport report = state.report;
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -78,7 +79,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colors: <Color>[
|
||||
UiColors.primary,
|
||||
UiColors.buttonPrimaryHover
|
||||
],
|
||||
@@ -88,9 +89,9 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -110,7 +111,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.daily_ops_report
|
||||
.title,
|
||||
@@ -189,7 +190,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Date Selector
|
||||
GestureDetector(
|
||||
onTap: () => _pickDate(context),
|
||||
@@ -198,7 +199,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 4,
|
||||
@@ -208,9 +209,9 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.calendar,
|
||||
size: 16,
|
||||
@@ -246,7 +247,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 1.2,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_OpsStatCard(
|
||||
label: context.t.client_reports
|
||||
.daily_ops_report.metrics.scheduled.label,
|
||||
@@ -339,7 +340,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
),
|
||||
)
|
||||
else
|
||||
...report.shifts.map((shift) => _ShiftListItem(
|
||||
...report.shifts.map((DailyOpsShift shift) => _ShiftListItem(
|
||||
title: shift.title,
|
||||
location: shift.location,
|
||||
time:
|
||||
@@ -376,11 +377,6 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
}
|
||||
|
||||
class _OpsStatCard extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final String subValue;
|
||||
final Color color;
|
||||
final IconData icon;
|
||||
|
||||
const _OpsStatCard({
|
||||
required this.label,
|
||||
@@ -389,6 +385,11 @@ class _OpsStatCard extends StatelessWidget {
|
||||
required this.color,
|
||||
required this.icon,
|
||||
});
|
||||
final String label;
|
||||
final String value;
|
||||
final String subValue;
|
||||
final Color color;
|
||||
final IconData icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -397,7 +398,7 @@ class _OpsStatCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 4,
|
||||
@@ -408,9 +409,9 @@ class _OpsStatCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 14, color: color),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
@@ -429,7 +430,7 @@ class _OpsStatCard extends StatelessWidget {
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
@@ -467,13 +468,6 @@ class _OpsStatCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ShiftListItem extends StatelessWidget {
|
||||
final String title;
|
||||
final String location;
|
||||
final String time;
|
||||
final String workers;
|
||||
final String rate;
|
||||
final String status;
|
||||
final Color statusColor;
|
||||
|
||||
const _ShiftListItem({
|
||||
required this.title,
|
||||
@@ -484,6 +478,13 @@ class _ShiftListItem extends StatelessWidget {
|
||||
required this.status,
|
||||
required this.statusColor,
|
||||
});
|
||||
final String title;
|
||||
final String location;
|
||||
final String time;
|
||||
final String workers;
|
||||
final String rate;
|
||||
final String status;
|
||||
final Color statusColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -493,7 +494,7 @@ class _ShiftListItem extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.02),
|
||||
blurRadius: 2,
|
||||
@@ -501,14 +502,14 @@ class _ShiftListItem extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
@@ -519,7 +520,7 @@ class _ShiftListItem extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.mapPin,
|
||||
size: 10,
|
||||
@@ -565,7 +566,7 @@ class _ShiftListItem extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_infoItem(
|
||||
context,
|
||||
UiIcons.clock,
|
||||
@@ -591,12 +592,12 @@ class _ShiftListItem extends StatelessWidget {
|
||||
Widget _infoItem(
|
||||
BuildContext context, IconData icon, String label, String value) {
|
||||
return Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 12, color: UiColors.textSecondary),
|
||||
const SizedBox(width: 6),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 10, color: UiColors.pinInactive),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/forecast/forecast_state.dart';
|
||||
@@ -24,12 +25,12 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<ForecastBloc>()
|
||||
create: (BuildContext context) => Modular.get<ForecastBloc>()
|
||||
..add(LoadForecastReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<ForecastBloc, ForecastState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ForecastState state) {
|
||||
if (state is ForecastLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -39,10 +40,10 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
}
|
||||
|
||||
if (state is ForecastLoaded) {
|
||||
final report = state.report;
|
||||
final ForecastReport report = state.report;
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Header
|
||||
_buildHeader(context),
|
||||
|
||||
@@ -53,7 +54,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Metrics Grid
|
||||
_buildMetricsGrid(context, report),
|
||||
const SizedBox(height: 16),
|
||||
@@ -82,7 +83,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
)
|
||||
else
|
||||
...report.weeklyBreakdown.map(
|
||||
(week) => _WeeklyBreakdownItem(week: week),
|
||||
(ForecastWeek week) => _WeeklyBreakdownItem(week: week),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
@@ -112,16 +113,16 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
decoration: const BoxDecoration(
|
||||
color: UiColors.primary,
|
||||
gradient: LinearGradient(
|
||||
colors: [UiColors.primary, Color(0xFF0020A0)], // Deep blue gradient
|
||||
colors: <Color>[UiColors.primary, Color(0xFF0020A0)], // Deep blue gradient
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -141,7 +142,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.forecast_report.title,
|
||||
style: UiTypography.headline3m.copyWith(color: UiColors.white),
|
||||
@@ -180,7 +181,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
}
|
||||
|
||||
Widget _buildMetricsGrid(BuildContext context, ForecastReport report) {
|
||||
final t = context.t.client_reports.forecast_report;
|
||||
final TranslationsClientReportsForecastReportEn t = context.t.client_reports.forecast_report;
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
shrinkWrap: true,
|
||||
@@ -188,7 +189,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 1.3,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_MetricCard(
|
||||
icon: UiIcons.dollar,
|
||||
label: t.metrics.four_week_forecast,
|
||||
@@ -232,7 +233,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -241,7 +242,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.forecast_report.chart_title,
|
||||
style: UiTypography.headline4m,
|
||||
@@ -257,9 +258,9 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// X Axis labels manually if chart doesn't handle them perfectly or for custom look
|
||||
Row(
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
children: <Widget>[
|
||||
Text('W1', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)),
|
||||
Text('W1', style: TextStyle(color: UiColors.transparent, fontSize: 12)), // Spacer
|
||||
Text('W2', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)),
|
||||
@@ -276,12 +277,6 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
}
|
||||
|
||||
class _MetricCard extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
final String badgeText;
|
||||
final Color iconColor;
|
||||
final Color badgeColor;
|
||||
|
||||
const _MetricCard({
|
||||
required this.icon,
|
||||
@@ -291,6 +286,12 @@ class _MetricCard extends StatelessWidget {
|
||||
required this.iconColor,
|
||||
required this.badgeColor,
|
||||
});
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final String value;
|
||||
final String badgeText;
|
||||
final Color iconColor;
|
||||
final Color badgeColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -299,7 +300,7 @@ class _MetricCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 8,
|
||||
@@ -309,9 +310,9 @@ class _MetricCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
@@ -349,13 +350,13 @@ class _MetricCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _WeeklyBreakdownItem extends StatelessWidget {
|
||||
final ForecastWeek week;
|
||||
|
||||
const _WeeklyBreakdownItem({required this.week});
|
||||
final ForecastWeek week;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t.client_reports.forecast_report.weekly_breakdown;
|
||||
final TranslationsClientReportsForecastReportWeeklyBreakdownEn t = context.t.client_reports.forecast_report.weekly_breakdown;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
@@ -365,10 +366,10 @@ class _WeeklyBreakdownItem extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
t.week(index: week.weekNumber),
|
||||
style: UiTypography.headline4m,
|
||||
@@ -391,7 +392,7 @@ class _WeeklyBreakdownItem extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
_buildStat(t.shifts, week.shiftsCount.toString()),
|
||||
_buildStat(t.hours, week.hoursCount.toStringAsFixed(0)),
|
||||
_buildStat(t.avg_shift, NumberFormat.currency(symbol: r'$', decimalDigits: 0).format(week.avgCostPerShift)),
|
||||
@@ -405,7 +406,7 @@ class _WeeklyBreakdownItem extends StatelessWidget {
|
||||
Widget _buildStat(String label, String value) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(label, style: UiTypography.footnote1r.textSecondary),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: UiTypography.body1m),
|
||||
@@ -415,9 +416,9 @@ class _WeeklyBreakdownItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ForecastChart extends StatelessWidget {
|
||||
final List<ForecastPoint> points;
|
||||
|
||||
const _ForecastChart({required this.points});
|
||||
final List<ForecastPoint> points;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -430,11 +431,11 @@ class _ForecastChart extends StatelessWidget {
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 5000, // Dynamic?
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(
|
||||
getDrawingHorizontalLine: (double value) {
|
||||
return const FlLine(
|
||||
color: UiColors.borderInactive,
|
||||
strokeWidth: 1,
|
||||
dashArray: [5, 5],
|
||||
dashArray: <int>[5, 5],
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -443,9 +444,9 @@ class _ForecastChart extends StatelessWidget {
|
||||
minX: 0,
|
||||
maxX: points.length.toDouble() - 1,
|
||||
// minY: 0, // Let it scale automatically
|
||||
lineBarsData: [
|
||||
lineBarsData: <LineChartBarData>[
|
||||
LineChartBarData(
|
||||
spots: points.asMap().entries.map((e) {
|
||||
spots: points.asMap().entries.map((MapEntry<int, ForecastPoint> e) {
|
||||
return FlSpot(e.key.toDouble(), e.value.projectedCost);
|
||||
}).toList(),
|
||||
isCurved: true,
|
||||
@@ -454,7 +455,7 @@ class _ForecastChart extends StatelessWidget {
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
getDotPainter: (FlSpot spot, double percent, LineChartBarData barData, int index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: UiColors.textWarning,
|
||||
@@ -473,3 +474,4 @@ class _ForecastChart extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/no_show/no_show_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/no_show/no_show_event.dart';
|
||||
@@ -17,18 +18,18 @@ class NoShowReportPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
|
||||
DateTime _endDate = DateTime.now();
|
||||
final DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
|
||||
final DateTime _endDate = DateTime.now();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<NoShowBloc>()
|
||||
create: (BuildContext context) => Modular.get<NoShowBloc>()
|
||||
..add(LoadNoShowReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<NoShowBloc, NoShowState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, NoShowState state) {
|
||||
if (state is NoShowLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -38,12 +39,12 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
}
|
||||
|
||||
if (state is NoShowLoaded) {
|
||||
final report = state.report;
|
||||
final uniqueWorkers = report.flaggedWorkers.length;
|
||||
final NoShowReport report = state.report;
|
||||
final int uniqueWorkers = report.flaggedWorkers.length;
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// ── Header ──────────────────────────────────────────
|
||||
children: <Widget>[
|
||||
// ── Header ──────────────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 60,
|
||||
@@ -53,7 +54,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colors: <Color>[
|
||||
UiColors.primary,
|
||||
UiColors.buttonPrimaryHover,
|
||||
],
|
||||
@@ -63,9 +64,9 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -85,7 +86,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.no_show_report.title,
|
||||
style: const TextStyle(
|
||||
@@ -150,17 +151,17 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
),
|
||||
),
|
||||
|
||||
// ── Content ─────────────────────────────────────────
|
||||
// ── Content ─────────────────────────────────────────
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// 3-chip summary row (matches prototype)
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _SummaryChip(
|
||||
icon: UiIcons.warning,
|
||||
@@ -220,7 +221,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
)
|
||||
else
|
||||
...report.flaggedWorkers.map(
|
||||
(worker) => _WorkerCard(worker: worker),
|
||||
(NoShowWorker worker) => _WorkerCard(worker: worker),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
@@ -240,12 +241,8 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Summary chip (top 3 stats) ───────────────────────────────────────────────
|
||||
// ── Summary chip (top 3 stats) ───────────────────────────────────────────────
|
||||
class _SummaryChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
const _SummaryChip({
|
||||
required this.icon,
|
||||
@@ -253,6 +250,10 @@ class _SummaryChip extends StatelessWidget {
|
||||
required this.label,
|
||||
required this.value,
|
||||
});
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -261,7 +262,7 @@ class _SummaryChip extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 8,
|
||||
@@ -271,9 +272,9 @@ class _SummaryChip extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 12, color: iconColor),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
@@ -304,11 +305,11 @@ class _SummaryChip extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Worker card with risk badge + latest incident ────────────────────────────
|
||||
// ── Worker card with risk badge + latest incident ────────────────────────────
|
||||
class _WorkerCard extends StatelessWidget {
|
||||
final NoShowWorker worker;
|
||||
|
||||
const _WorkerCard({required this.worker});
|
||||
final NoShowWorker worker;
|
||||
|
||||
String _riskLabel(BuildContext context, int count) {
|
||||
if (count >= 3) return context.t.client_reports.no_show_report.risks.high;
|
||||
@@ -330,9 +331,9 @@ class _WorkerCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final riskLabel = _riskLabel(context, worker.noShowCount);
|
||||
final riskColor = _riskColor(worker.noShowCount);
|
||||
final riskBg = _riskBg(worker.noShowCount);
|
||||
final String riskLabel = _riskLabel(context, worker.noShowCount);
|
||||
final Color riskColor = _riskColor(worker.noShowCount);
|
||||
final Color riskBg = _riskBg(worker.noShowCount);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
@@ -340,7 +341,7 @@ class _WorkerCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 6,
|
||||
@@ -348,12 +349,12 @@ class _WorkerCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
@@ -370,7 +371,7 @@ class _WorkerCard extends StatelessWidget {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
worker.fullName,
|
||||
style: const TextStyle(
|
||||
@@ -416,7 +417,7 @@ class _WorkerCard extends StatelessWidget {
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.no_show_report.latest_incident,
|
||||
style: const TextStyle(
|
||||
@@ -447,4 +448,5 @@ class _WorkerCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Insight line ─────────────────────────────────────────────────────────────
|
||||
// ── Insight line ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/src/entities/reports/performance_report.dart';
|
||||
|
||||
class PerformanceReportPage extends StatefulWidget {
|
||||
const PerformanceReportPage({super.key});
|
||||
@@ -15,18 +16,18 @@ class PerformanceReportPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
|
||||
DateTime _endDate = DateTime.now();
|
||||
final DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
|
||||
final DateTime _endDate = DateTime.now();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<PerformanceBloc>()
|
||||
create: (BuildContext context) => Modular.get<PerformanceBloc>()
|
||||
..add(LoadPerformanceReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<PerformanceBloc, PerformanceState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, PerformanceState state) {
|
||||
if (state is PerformanceLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -36,10 +37,10 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
}
|
||||
|
||||
if (state is PerformanceLoaded) {
|
||||
final report = state.report;
|
||||
final PerformanceReport report = state.report;
|
||||
|
||||
// Compute overall score (0–100) from the 4 KPIs
|
||||
final overallScore = ((report.fillRate * 0.3) +
|
||||
final double overallScore = ((report.fillRate * 0.3) +
|
||||
(report.completionRate * 0.3) +
|
||||
(report.onTimeRate * 0.25) +
|
||||
// avg fill time: 3h target → invert to score
|
||||
@@ -49,24 +50,24 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
0.15))
|
||||
.clamp(0.0, 100.0);
|
||||
|
||||
final scoreLabel = overallScore >= 90
|
||||
final String scoreLabel = overallScore >= 90
|
||||
? context.t.client_reports.performance_report.overall_score.excellent
|
||||
: overallScore >= 75
|
||||
? context.t.client_reports.performance_report.overall_score.good
|
||||
: context.t.client_reports.performance_report.overall_score.needs_work;
|
||||
final scoreLabelColor = overallScore >= 90
|
||||
final Color scoreLabelColor = overallScore >= 90
|
||||
? UiColors.success
|
||||
: overallScore >= 75
|
||||
? UiColors.textWarning
|
||||
: UiColors.error;
|
||||
final scoreLabelBg = overallScore >= 90
|
||||
final Color scoreLabelBg = overallScore >= 90
|
||||
? UiColors.tagSuccess
|
||||
: overallScore >= 75
|
||||
? UiColors.tagPending
|
||||
: UiColors.tagError;
|
||||
|
||||
// KPI rows: label, value, target, color, met status
|
||||
final kpis = [
|
||||
final List<_KpiData> kpis = <_KpiData>[
|
||||
_KpiData(
|
||||
icon: UiIcons.users,
|
||||
iconColor: UiColors.primary,
|
||||
@@ -119,7 +120,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// ── Header ───────────────────────────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -130,16 +131,16 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [UiColors.primary, UiColors.buttonPrimaryHover],
|
||||
colors: <Color>[UiColors.primary, UiColors.buttonPrimaryHover],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -159,7 +160,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.performance_report
|
||||
.title,
|
||||
@@ -229,7 +230,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// ── Overall Score Hero Card ───────────────────
|
||||
Container(
|
||||
width: double.infinity,
|
||||
@@ -240,7 +241,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0F4FF),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -249,7 +250,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.chart,
|
||||
size: 32,
|
||||
@@ -258,7 +259,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
context.t.client_reports.performance_report.overall_score.title,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: UiColors.textSecondary,
|
||||
),
|
||||
@@ -303,7 +304,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -312,7 +313,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.performance_report.kpis_title,
|
||||
style: const TextStyle(
|
||||
@@ -324,7 +325,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
...kpis.map(
|
||||
(kpi) => _KpiRow(kpi: kpi),
|
||||
(_KpiData kpi) => _KpiRow(kpi: kpi),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -349,15 +350,6 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
|
||||
// ── KPI data model ────────────────────────────────────────────────────────────
|
||||
class _KpiData {
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String target;
|
||||
final double value; // 0–100 for bar
|
||||
final String displayValue;
|
||||
final Color barColor;
|
||||
final bool met;
|
||||
final bool close;
|
||||
|
||||
const _KpiData({
|
||||
required this.icon,
|
||||
@@ -370,27 +362,36 @@ class _KpiData {
|
||||
required this.met,
|
||||
required this.close,
|
||||
});
|
||||
final IconData icon;
|
||||
final Color iconColor;
|
||||
final String label;
|
||||
final String target;
|
||||
final double value; // 0–100 for bar
|
||||
final String displayValue;
|
||||
final Color barColor;
|
||||
final bool met;
|
||||
final bool close;
|
||||
}
|
||||
|
||||
// ── KPI row widget ────────────────────────────────────────────────────────────
|
||||
class _KpiRow extends StatelessWidget {
|
||||
final _KpiData kpi;
|
||||
|
||||
const _KpiRow({required this.kpi});
|
||||
final _KpiData kpi;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final badgeText = kpi.met
|
||||
final String badgeText = kpi.met
|
||||
? context.t.client_reports.performance_report.kpis.met
|
||||
: kpi.close
|
||||
? context.t.client_reports.performance_report.kpis.close
|
||||
: context.t.client_reports.performance_report.kpis.miss;
|
||||
final badgeColor = kpi.met
|
||||
final Color badgeColor = kpi.met
|
||||
? UiColors.success
|
||||
: kpi.close
|
||||
? UiColors.textWarning
|
||||
: UiColors.error;
|
||||
final badgeBg = kpi.met
|
||||
final Color badgeBg = kpi.met
|
||||
? UiColors.tagSuccess
|
||||
: kpi.close
|
||||
? UiColors.tagPending
|
||||
@@ -399,9 +400,9 @@ class _KpiRow extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
@@ -415,7 +416,7 @@ class _KpiRow extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
kpi.label,
|
||||
style: const TextStyle(
|
||||
@@ -437,7 +438,7 @@ class _KpiRow extends StatelessWidget {
|
||||
// Value + badge inline (matches prototype)
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
kpi.displayValue,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -24,7 +24,7 @@ class _ReportsPageState extends State<ReportsPage>
|
||||
late ReportsSummaryBloc _summaryBloc;
|
||||
|
||||
// Date ranges per tab: Today, Week, Month, Quarter
|
||||
final List<(DateTime, DateTime)> _dateRanges = [
|
||||
final List<(DateTime, DateTime)> _dateRanges = <(DateTime, DateTime)>[
|
||||
(
|
||||
DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day),
|
||||
DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day,
|
||||
@@ -64,7 +64,7 @@ class _ReportsPageState extends State<ReportsPage>
|
||||
}
|
||||
|
||||
void _loadSummary(int tabIndex) {
|
||||
final range = _dateRanges[tabIndex];
|
||||
final (DateTime, DateTime) range = _dateRanges[tabIndex];
|
||||
_summaryBloc.add(LoadReportsSummary(
|
||||
startDate: range.$1,
|
||||
endDate: range.$2,
|
||||
@@ -85,7 +85,7 @@ class _ReportsPageState extends State<ReportsPage>
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Header with title and tabs
|
||||
ReportsHeader(
|
||||
tabController: _tabController,
|
||||
@@ -93,20 +93,20 @@ class _ReportsPageState extends State<ReportsPage>
|
||||
),
|
||||
|
||||
// Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Key Metrics Grid
|
||||
const MetricsGrid(),
|
||||
MetricsGrid(),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(height: 24),
|
||||
|
||||
// Quick Reports Section
|
||||
const QuickReportsSection(),
|
||||
QuickReportsSection(),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_event.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/spend/spend_state.dart';
|
||||
@@ -24,10 +25,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final now = DateTime.now();
|
||||
final DateTime now = DateTime.now();
|
||||
// Monday alignment logic
|
||||
final diff = now.weekday - DateTime.monday;
|
||||
final monday = now.subtract(Duration(days: diff));
|
||||
final int diff = now.weekday - DateTime.monday;
|
||||
final DateTime monday = now.subtract(Duration(days: diff));
|
||||
_startDate = DateTime(monday.year, monday.month, monday.day);
|
||||
_endDate = _startDate.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59));
|
||||
}
|
||||
@@ -35,12 +36,12 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => Modular.get<SpendBloc>()
|
||||
create: (BuildContext context) => Modular.get<SpendBloc>()
|
||||
..add(LoadSpendReport(startDate: _startDate, endDate: _endDate)),
|
||||
child: Scaffold(
|
||||
backgroundColor: UiColors.bgMenu,
|
||||
body: BlocBuilder<SpendBloc, SpendState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, SpendState state) {
|
||||
if (state is SpendLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -50,10 +51,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
}
|
||||
|
||||
if (state is SpendLoaded) {
|
||||
final report = state.report;
|
||||
final SpendReport report = state.report;
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Header
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
@@ -67,9 +68,9 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Container(
|
||||
@@ -89,7 +90,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.spend_report.title,
|
||||
style: const TextStyle(
|
||||
@@ -167,10 +168,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Summary Cards (New Style)
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: _SpendStatCard(
|
||||
label: context.t.client_reports.spend_report
|
||||
@@ -209,7 +210,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -219,7 +220,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.spend_report.chart_title,
|
||||
style: const TextStyle(
|
||||
@@ -262,9 +263,9 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
}
|
||||
|
||||
class _SpendBarChart extends StatelessWidget {
|
||||
final List<dynamic> chartData;
|
||||
|
||||
const _SpendBarChart({required this.chartData});
|
||||
final List<dynamic> chartData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -272,14 +273,14 @@ class _SpendBarChart extends StatelessWidget {
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: (chartData.fold<double>(0,
|
||||
(prev, element) =>
|
||||
(double prev, element) =>
|
||||
element.amount > prev ? element.amount : prev) *
|
||||
1.2)
|
||||
.ceilToDouble(),
|
||||
barTouchData: BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
getTooltipItem: (BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex) {
|
||||
return BarTooltipItem(
|
||||
'\$${rod.toY.round()}',
|
||||
const TextStyle(
|
||||
@@ -296,7 +297,7 @@ class _SpendBarChart extends StatelessWidget {
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: (value, meta) {
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
if (value.toInt() >= chartData.length) return const SizedBox();
|
||||
final date = chartData[value.toInt()].date;
|
||||
return SideTitleWidget(
|
||||
@@ -317,7 +318,7 @@ class _SpendBarChart extends StatelessWidget {
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: (value, meta) {
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
if (value == 0) return const SizedBox();
|
||||
return SideTitleWidget(
|
||||
axisSide: meta.axisSide,
|
||||
@@ -343,7 +344,7 @@ class _SpendBarChart extends StatelessWidget {
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 1000,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
getDrawingHorizontalLine: (double value) => const FlLine(
|
||||
color: UiColors.bgSecondary,
|
||||
strokeWidth: 1,
|
||||
),
|
||||
@@ -351,9 +352,9 @@ class _SpendBarChart extends StatelessWidget {
|
||||
borderData: FlBorderData(show: false),
|
||||
barGroups: List.generate(
|
||||
chartData.length,
|
||||
(index) => BarChartGroupData(
|
||||
(int index) => BarChartGroupData(
|
||||
x: index,
|
||||
barRods: [
|
||||
barRods: <BarChartRodData>[
|
||||
BarChartRodData(
|
||||
toY: chartData[index].amount,
|
||||
color: UiColors.success,
|
||||
@@ -371,11 +372,6 @@ class _SpendBarChart extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SpendStatCard extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final String pillText;
|
||||
final Color themeColor;
|
||||
final IconData icon;
|
||||
|
||||
const _SpendStatCard({
|
||||
required this.label,
|
||||
@@ -384,6 +380,11 @@ class _SpendStatCard extends StatelessWidget {
|
||||
required this.themeColor,
|
||||
required this.icon,
|
||||
});
|
||||
final String label;
|
||||
final String value;
|
||||
final String pillText;
|
||||
final Color themeColor;
|
||||
final IconData icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -392,7 +393,7 @@ class _SpendStatCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 8,
|
||||
@@ -402,9 +403,9 @@ class _SpendStatCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 14, color: themeColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
@@ -453,9 +454,9 @@ class _SpendStatCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SpendByIndustryCard extends StatelessWidget {
|
||||
final List<SpendIndustryCategory> industries;
|
||||
|
||||
const _SpendByIndustryCard({required this.industries});
|
||||
final List<SpendIndustryCategory> industries;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -464,7 +465,7 @@ class _SpendByIndustryCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
blurRadius: 10,
|
||||
@@ -474,7 +475,7 @@ class _SpendByIndustryCard extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_reports.spend_report.spend_by_industry,
|
||||
style: const TextStyle(
|
||||
@@ -495,14 +496,14 @@ class _SpendByIndustryCard extends StatelessWidget {
|
||||
),
|
||||
)
|
||||
else
|
||||
...industries.map((ind) => Padding(
|
||||
...industries.map((SpendIndustryCategory ind) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
ind.name,
|
||||
style: const TextStyle(
|
||||
@@ -547,3 +548,4 @@ class _SpendByIndustryCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,17 @@ import 'package:flutter/material.dart';
|
||||
/// Shows a metric with an icon, label, value, and a badge with contextual
|
||||
/// information. Used in the metrics grid of the reports page.
|
||||
class MetricCard extends StatelessWidget {
|
||||
|
||||
const MetricCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.badgeText,
|
||||
required this.badgeColor,
|
||||
required this.badgeTextColor,
|
||||
required this.iconColor,
|
||||
});
|
||||
/// The icon to display for this metric.
|
||||
final IconData icon;
|
||||
|
||||
@@ -27,17 +38,6 @@ class MetricCard extends StatelessWidget {
|
||||
/// Color for the icon.
|
||||
final Color iconColor;
|
||||
|
||||
const MetricCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.badgeText,
|
||||
required this.badgeColor,
|
||||
required this.badgeTextColor,
|
||||
required this.iconColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -45,7 +45,7 @@ class MetricCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
blurRadius: 4,
|
||||
@@ -56,10 +56,10 @@ class MetricCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Icon and Label
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 16, color: iconColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
@@ -78,7 +78,7 @@ class MetricCard extends StatelessWidget {
|
||||
// Value and Badge
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/src/entities/reports/reports_summary.dart';
|
||||
|
||||
import 'metric_card.dart';
|
||||
|
||||
@@ -25,7 +26,7 @@ class MetricsGrid extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ReportsSummaryBloc, ReportsSummaryState>(
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ReportsSummaryState state) {
|
||||
// Loading or Initial State
|
||||
if (state is ReportsSummaryLoading || state is ReportsSummaryInitial) {
|
||||
return const Padding(
|
||||
@@ -45,7 +46,7 @@ class MetricsGrid extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.warning,
|
||||
color: UiColors.error, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
@@ -63,8 +64,8 @@ class MetricsGrid extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Loaded State
|
||||
final summary = (state as ReportsSummaryLoaded).summary;
|
||||
final currencyFmt = NumberFormat.currency(
|
||||
final ReportsSummary summary = (state as ReportsSummaryLoaded).summary;
|
||||
final NumberFormat currencyFmt = NumberFormat.currency(
|
||||
symbol: '\$', decimalDigits: 0);
|
||||
|
||||
return GridView.count(
|
||||
@@ -74,7 +75,7 @@ class MetricsGrid extends StatelessWidget {
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 1.2,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Total Hours
|
||||
MetricCard(
|
||||
icon: UiIcons.clock,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -18,7 +19,7 @@ class QuickReportsSection extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Title
|
||||
Text(
|
||||
context.t.client_reports.quick_reports.title,
|
||||
@@ -33,7 +34,7 @@ class QuickReportsSection extends StatelessWidget {
|
||||
mainAxisSpacing: 12,
|
||||
crossAxisSpacing: 12,
|
||||
childAspectRatio: 1.3,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Daily Operations
|
||||
ReportCard(
|
||||
icon: UiIcons.calendar,
|
||||
@@ -89,3 +90,4 @@ class QuickReportsSection extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,15 @@ import 'package:flutter_modular/flutter_modular.dart';
|
||||
/// Displays an icon, name, and a quick navigation to a report page.
|
||||
/// Used in the quick reports grid of the reports page.
|
||||
class ReportCard extends StatelessWidget {
|
||||
|
||||
const ReportCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.name,
|
||||
required this.iconBgColor,
|
||||
required this.iconColor,
|
||||
required this.route,
|
||||
});
|
||||
/// The icon to display for this report.
|
||||
final IconData icon;
|
||||
|
||||
@@ -23,15 +32,6 @@ class ReportCard extends StatelessWidget {
|
||||
/// Navigation route to the report page.
|
||||
final String route;
|
||||
|
||||
const ReportCard({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.name,
|
||||
required this.iconBgColor,
|
||||
required this.iconColor,
|
||||
required this.route,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
@@ -41,7 +41,7 @@ class ReportCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.02),
|
||||
blurRadius: 2,
|
||||
@@ -51,7 +51,7 @@ class ReportCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Icon Container
|
||||
Container(
|
||||
width: 40,
|
||||
@@ -65,7 +65,7 @@ class ReportCard extends StatelessWidget {
|
||||
// Name and Export Info
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
@@ -78,7 +78,7 @@ class ReportCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.download,
|
||||
size: 12,
|
||||
|
||||
@@ -32,7 +32,7 @@ class ReportsHeader extends StatelessWidget {
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
colors: <Color>[
|
||||
UiColors.primary,
|
||||
UiColors.buttonPrimaryHover,
|
||||
],
|
||||
@@ -41,10 +41,10 @@ class ReportsHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Title and Back Button
|
||||
Row(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => Modular.to.toClientHome(),
|
||||
child: Container(
|
||||
@@ -104,7 +104,7 @@ class ReportsHeader extends StatelessWidget {
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
dividerColor: Colors.transparent,
|
||||
tabs: [
|
||||
tabs: <Widget>[
|
||||
Tab(text: context.t.client_reports.tabs.today),
|
||||
Tab(text: context.t.client_reports.tabs.week),
|
||||
Tab(text: context.t.client_reports.tabs.month),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:client_reports/src/data/repositories_impl/reports_repository_impl.dart';
|
||||
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
|
||||
@@ -19,7 +20,7 @@ import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
|
||||
class ReportsModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => [DataConnectModule()];
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
@@ -44,3 +45,4 @@ class ReportsModule extends Module {
|
||||
r.child('/no-show', child: (_) => const NoShowReportPage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import '../repositories/settings_repository_interface.dart';
|
||||
///
|
||||
/// This use case delegates the sign out logic to the [SettingsRepositoryInterface].
|
||||
class SignOutUseCase implements NoInputUseCase<void> {
|
||||
final SettingsRepositoryInterface _repository;
|
||||
|
||||
/// Creates a [SignOutUseCase].
|
||||
///
|
||||
/// Requires a [SettingsRepositoryInterface] to perform the sign out operation.
|
||||
SignOutUseCase(this._repository);
|
||||
final SettingsRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<void> call() {
|
||||
|
||||
@@ -9,13 +9,13 @@ part 'client_settings_state.dart';
|
||||
/// BLoC to manage client settings and profile state.
|
||||
class ClientSettingsBloc extends Bloc<ClientSettingsEvent, ClientSettingsState>
|
||||
with BlocErrorHandler<ClientSettingsState> {
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
ClientSettingsBloc({required SignOutUseCase signOutUseCase})
|
||||
: _signOutUseCase = signOutUseCase,
|
||||
super(const ClientSettingsInitial()) {
|
||||
on<ClientSettingsSignOutRequested>(_onSignOutRequested);
|
||||
}
|
||||
final SignOutUseCase _signOutUseCase;
|
||||
|
||||
Future<void> _onSignOutRequested(
|
||||
ClientSettingsSignOutRequested event,
|
||||
@@ -23,7 +23,7 @@ class ClientSettingsBloc extends Bloc<ClientSettingsEvent, ClientSettingsState>
|
||||
) async {
|
||||
emit(const ClientSettingsLoading());
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _signOutUseCase();
|
||||
emit(const ClientSettingsSignOutSuccess());
|
||||
|
||||
@@ -20,9 +20,9 @@ class ClientSettingsSignOutSuccess extends ClientSettingsState {
|
||||
}
|
||||
|
||||
class ClientSettingsError extends ClientSettingsState {
|
||||
final String message;
|
||||
|
||||
const ClientSettingsError(this.message);
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[message];
|
||||
|
||||
@@ -110,9 +110,9 @@ class SettingsActions extends StatelessWidget {
|
||||
|
||||
/// Quick Links card — inline here since it's always part of SettingsActions ordering.
|
||||
class _QuickLinksCard extends StatelessWidget {
|
||||
final TranslationsClientSettingsProfileEn labels;
|
||||
|
||||
const _QuickLinksCard({required this.labels});
|
||||
final TranslationsClientSettingsProfileEn labels;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -152,15 +152,15 @@ class _QuickLinksCard extends StatelessWidget {
|
||||
|
||||
/// A single quick link row item.
|
||||
class _QuickLinkItem extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _QuickLinkItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -75,7 +75,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
color: UiColors.white.withValues(alpha: 0.6),
|
||||
width: 3,
|
||||
),
|
||||
boxShadow: [
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withValues(alpha: 0.15),
|
||||
blurRadius: 16,
|
||||
|
||||
@@ -56,6 +56,13 @@ class SettingsQuickLinks extends StatelessWidget {
|
||||
|
||||
/// Internal widget for a single quick link item.
|
||||
class _QuickLinkItem extends StatelessWidget {
|
||||
|
||||
/// Creates a [_QuickLinkItem].
|
||||
const _QuickLinkItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
/// The icon to display.
|
||||
final IconData icon;
|
||||
|
||||
@@ -65,13 +72,6 @@ class _QuickLinkItem extends StatelessWidget {
|
||||
/// Callback when the link is tapped.
|
||||
final VoidCallback onTap;
|
||||
|
||||
/// Creates a [_QuickLinkItem].
|
||||
const _QuickLinkItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
/// Builds the quick link item UI.
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import '../../domain/repositories/i_view_orders_repository.dart';
|
||||
|
||||
/// Implementation of [IViewOrdersRepository] using Data Connect.
|
||||
class ViewOrdersRepositoryImpl implements IViewOrdersRepository {
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
ViewOrdersRepositoryImpl({
|
||||
required dc.DataConnectService service,
|
||||
}) : _service = service;
|
||||
final dc.DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<List<domain.OrderItem>> getOrdersForRange({
|
||||
|
||||
@@ -9,10 +9,10 @@ import '../arguments/orders_range_arguments.dart';
|
||||
/// and delegates the data retrieval to the [IViewOrdersRepository].
|
||||
class GetOrdersUseCase
|
||||
implements UseCase<OrdersRangeArguments, List<OrderItem>> {
|
||||
final IViewOrdersRepository _repository;
|
||||
|
||||
/// Creates a [GetOrdersUseCase] with the required [IViewOrdersRepository].
|
||||
GetOrdersUseCase(this._repository);
|
||||
final IViewOrdersRepository _repository;
|
||||
|
||||
@override
|
||||
Future<List<OrderItem>> call(OrdersRangeArguments input) {
|
||||
|
||||
@@ -847,12 +847,12 @@ class _OrderEditSheetState extends State<_OrderEditSheet> {
|
||||
.toList();
|
||||
|
||||
await _loadVendorsAndSelect(firstShift.order.vendorId);
|
||||
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub?
|
||||
final dc.ListShiftRolesByBusinessAndOrderShiftRolesShiftOrderTeamHub
|
||||
teamHub = firstShift.order.teamHub;
|
||||
await _loadHubsAndSelect(
|
||||
placeId: teamHub?.placeId,
|
||||
hubName: teamHub?.hubName,
|
||||
address: teamHub?.address,
|
||||
placeId: teamHub.placeId,
|
||||
hubName: teamHub.hubName,
|
||||
address: teamHub.address,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
|
||||
@@ -6,13 +6,13 @@ import 'package:krow_core/core.dart';
|
||||
import '../../domain/repositories/place_repository.dart';
|
||||
|
||||
class PlaceRepositoryImpl implements PlaceRepository {
|
||||
final http.Client _client;
|
||||
|
||||
PlaceRepositoryImpl({http.Client? client}) : _client = client ?? http.Client();
|
||||
final http.Client _client;
|
||||
|
||||
@override
|
||||
Future<List<String>> searchCities(String query) async {
|
||||
if (query.isEmpty) return [];
|
||||
if (query.isEmpty) return <String>[];
|
||||
|
||||
final Uri uri = Uri.https(
|
||||
'maps.googleapis.com',
|
||||
@@ -39,7 +39,7 @@ class PlaceRepositoryImpl implements PlaceRepository {
|
||||
} else {
|
||||
// Handle other statuses (OVER_QUERY_LIMIT, REQUEST_DENIED, etc.)
|
||||
// Returning empty list for now to avoid crashing UI, ideally log this.
|
||||
return [];
|
||||
return <String>[];
|
||||
}
|
||||
} else {
|
||||
throw Exception('Network Error: ${response.statusCode}');
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||
import '../../domain/repositories/profile_setup_repository.dart';
|
||||
|
||||
class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
||||
final DataConnectService _service;
|
||||
|
||||
ProfileSetupRepositoryImpl() : _service = DataConnectService.instance;
|
||||
final DataConnectService _service;
|
||||
|
||||
@override
|
||||
Future<void> submitProfile({
|
||||
|
||||
@@ -4,13 +4,13 @@ import 'package:krow_core/core.dart';
|
||||
///
|
||||
/// Encapsulates the phone number needed to initiate the sign-in process.
|
||||
class SignInWithPhoneArguments extends UseCaseArgument {
|
||||
/// The phone number to be used for sign-in or sign-up.
|
||||
final String phoneNumber;
|
||||
|
||||
/// Creates a [SignInWithPhoneArguments] instance.
|
||||
///
|
||||
/// The [phoneNumber] is required.
|
||||
const SignInWithPhoneArguments({required this.phoneNumber});
|
||||
/// The phone number to be used for sign-in or sign-up.
|
||||
final String phoneNumber;
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[phoneNumber];
|
||||
|
||||
@@ -6,14 +6,6 @@ import '../ui_entities/auth_mode.dart';
|
||||
/// Encapsulates the verification ID and the SMS code needed to verify
|
||||
/// a phone number during the authentication process.
|
||||
class VerifyOtpArguments extends UseCaseArgument {
|
||||
/// The unique identifier received after requesting an OTP.
|
||||
final String verificationId;
|
||||
|
||||
/// The one-time password (OTP) sent to the user's phone.
|
||||
final String smsCode;
|
||||
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
/// Creates a [VerifyOtpArguments] instance.
|
||||
///
|
||||
@@ -23,6 +15,14 @@ class VerifyOtpArguments extends UseCaseArgument {
|
||||
required this.smsCode,
|
||||
required this.mode,
|
||||
});
|
||||
/// The unique identifier received after requesting an OTP.
|
||||
final String verificationId;
|
||||
|
||||
/// The one-time password (OTP) sent to the user's phone.
|
||||
final String smsCode;
|
||||
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[verificationId, smsCode, mode];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
abstract class ProfileSetupRepository {
|
||||
Future<void> submitProfile({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import '../repositories/place_repository.dart';
|
||||
|
||||
class SearchCitiesUseCase {
|
||||
final PlaceRepository _repository;
|
||||
|
||||
SearchCitiesUseCase(this._repository);
|
||||
final PlaceRepository _repository;
|
||||
|
||||
Future<List<String>> call(String query) {
|
||||
return _repository.searchCities(query);
|
||||
|
||||
@@ -7,12 +7,12 @@ import '../repositories/auth_repository_interface.dart';
|
||||
/// This use case delegates the sign-in logic to the [AuthRepositoryInterface].
|
||||
class SignInWithPhoneUseCase
|
||||
implements UseCase<SignInWithPhoneArguments, String?> {
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
/// Creates a [SignInWithPhoneUseCase].
|
||||
///
|
||||
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
|
||||
SignInWithPhoneUseCase(this._repository);
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<String?> call(SignInWithPhoneArguments arguments) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import '../repositories/profile_setup_repository.dart';
|
||||
|
||||
class SubmitProfileSetup {
|
||||
final ProfileSetupRepository repository;
|
||||
|
||||
SubmitProfileSetup(this.repository);
|
||||
final ProfileSetupRepository repository;
|
||||
|
||||
Future<void> call({
|
||||
required String fullName,
|
||||
|
||||
@@ -7,12 +7,12 @@ import '../repositories/auth_repository_interface.dart';
|
||||
///
|
||||
/// This use case delegates the OTP verification logic to the [AuthRepositoryInterface].
|
||||
class VerifyOtpUseCase implements UseCase<VerifyOtpArguments, User?> {
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
/// Creates a [VerifyOtpUseCase].
|
||||
///
|
||||
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
|
||||
VerifyOtpUseCase(this._repository);
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
Future<User?> call(VerifyOtpArguments arguments) {
|
||||
|
||||
@@ -14,16 +14,6 @@ import 'auth_state.dart';
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
with BlocErrorHandler<AuthState>
|
||||
implements Disposable {
|
||||
/// The use case for signing in with a phone number.
|
||||
final SignInWithPhoneUseCase _signInUseCase;
|
||||
|
||||
/// The use case for verifying an OTP.
|
||||
final VerifyOtpUseCase _verifyOtpUseCase;
|
||||
int _requestToken = 0;
|
||||
DateTime? _lastCodeRequestAt;
|
||||
DateTime? _cooldownUntil;
|
||||
static const Duration _resendCooldown = Duration(seconds: 31);
|
||||
Timer? _cooldownTimer;
|
||||
|
||||
/// Creates an [AuthBloc].
|
||||
AuthBloc({
|
||||
@@ -40,6 +30,16 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
on<AuthResetRequested>(_onResetRequested);
|
||||
on<AuthCooldownTicked>(_onCooldownTicked);
|
||||
}
|
||||
/// The use case for signing in with a phone number.
|
||||
final SignInWithPhoneUseCase _signInUseCase;
|
||||
|
||||
/// The use case for verifying an OTP.
|
||||
final VerifyOtpUseCase _verifyOtpUseCase;
|
||||
int _requestToken = 0;
|
||||
DateTime? _lastCodeRequestAt;
|
||||
DateTime? _cooldownUntil;
|
||||
static const Duration _resendCooldown = Duration(seconds: 31);
|
||||
Timer? _cooldownTimer;
|
||||
|
||||
/// Clears any authentication error from the state.
|
||||
void _onErrorCleared(AuthErrorCleared event, Emitter<AuthState> emit) {
|
||||
@@ -111,7 +111,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
);
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final String? verificationId = await _signInUseCase(
|
||||
SignInWithPhoneArguments(
|
||||
@@ -193,7 +193,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
) async {
|
||||
emit(state.copyWith(status: AuthStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final User? user = await _verifyOtpUseCase(
|
||||
VerifyOtpArguments(
|
||||
|
||||
@@ -10,14 +10,14 @@ abstract class AuthEvent extends Equatable {
|
||||
|
||||
/// Event for requesting a sign-in with a phone number.
|
||||
class AuthSignInRequested extends AuthEvent {
|
||||
|
||||
const AuthSignInRequested({this.phoneNumber, required this.mode});
|
||||
/// The phone number provided by the user.
|
||||
final String? phoneNumber;
|
||||
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
const AuthSignInRequested({this.phoneNumber, required this.mode});
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[phoneNumber, mode];
|
||||
}
|
||||
@@ -27,6 +27,12 @@ class AuthSignInRequested extends AuthEvent {
|
||||
/// This event is dispatched after the user has received an OTP and
|
||||
/// submits it for verification.
|
||||
class AuthOtpSubmitted extends AuthEvent {
|
||||
|
||||
const AuthOtpSubmitted({
|
||||
required this.verificationId,
|
||||
required this.smsCode,
|
||||
required this.mode,
|
||||
});
|
||||
/// The verification ID received after the phone number submission.
|
||||
final String verificationId;
|
||||
|
||||
@@ -36,12 +42,6 @@ class AuthOtpSubmitted extends AuthEvent {
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
const AuthOtpSubmitted({
|
||||
required this.verificationId,
|
||||
required this.smsCode,
|
||||
required this.mode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[verificationId, smsCode, mode];
|
||||
}
|
||||
@@ -51,10 +51,10 @@ class AuthErrorCleared extends AuthEvent {}
|
||||
|
||||
/// Event for resetting the authentication flow back to initial.
|
||||
class AuthResetRequested extends AuthEvent {
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
const AuthResetRequested({required this.mode});
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[mode];
|
||||
@@ -62,9 +62,9 @@ class AuthResetRequested extends AuthEvent {
|
||||
|
||||
/// Event for ticking down the resend cooldown.
|
||||
class AuthCooldownTicked extends AuthEvent {
|
||||
final int secondsRemaining;
|
||||
|
||||
const AuthCooldownTicked(this.secondsRemaining);
|
||||
final int secondsRemaining;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[secondsRemaining];
|
||||
@@ -72,10 +72,10 @@ class AuthCooldownTicked extends AuthEvent {
|
||||
|
||||
/// Event for updating the current draft OTP in the state.
|
||||
class AuthOtpUpdated extends AuthEvent {
|
||||
/// The current draft OTP.
|
||||
final String otp;
|
||||
|
||||
const AuthOtpUpdated(this.otp);
|
||||
/// The current draft OTP.
|
||||
final String otp;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[otp];
|
||||
@@ -83,10 +83,10 @@ class AuthOtpUpdated extends AuthEvent {
|
||||
|
||||
/// Event for updating the current draft phone number in the state.
|
||||
class AuthPhoneUpdated extends AuthEvent {
|
||||
/// The current draft phone number.
|
||||
final String phoneNumber;
|
||||
|
||||
const AuthPhoneUpdated(this.phoneNumber);
|
||||
/// The current draft phone number.
|
||||
final String phoneNumber;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[phoneNumber];
|
||||
|
||||
@@ -22,6 +22,17 @@ enum AuthStatus {
|
||||
|
||||
/// A unified state class for the authentication process.
|
||||
class AuthState extends Equatable {
|
||||
|
||||
const AuthState({
|
||||
this.status = AuthStatus.initial,
|
||||
this.verificationId,
|
||||
this.mode = AuthMode.login,
|
||||
this.otp = '',
|
||||
this.phoneNumber = '',
|
||||
this.errorMessage,
|
||||
this.cooldownSecondsRemaining = 0,
|
||||
this.user,
|
||||
});
|
||||
/// The current status of the authentication flow.
|
||||
final AuthStatus status;
|
||||
|
||||
@@ -46,17 +57,6 @@ class AuthState extends Equatable {
|
||||
/// The authenticated user's data (available when status is [AuthStatus.authenticated]).
|
||||
final User? user;
|
||||
|
||||
const AuthState({
|
||||
this.status = AuthStatus.initial,
|
||||
this.verificationId,
|
||||
this.mode = AuthMode.login,
|
||||
this.otp = '',
|
||||
this.phoneNumber = '',
|
||||
this.errorMessage,
|
||||
this.cooldownSecondsRemaining = 0,
|
||||
this.user,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
status,
|
||||
|
||||
@@ -89,7 +89,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
emit(state.copyWith(status: ProfileSetupStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _submitProfileSetup(
|
||||
fullName: state.fullName,
|
||||
@@ -114,18 +114,18 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
Emitter<ProfileSetupState> emit,
|
||||
) async {
|
||||
if (event.query.isEmpty) {
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
emit(state.copyWith(locationSuggestions: <String>[]));
|
||||
return;
|
||||
}
|
||||
|
||||
// For search, we might want to handle errors silently or distinctively
|
||||
// Using simple try-catch here as it's a search-as-you-type feature where error dialogs are intrusive
|
||||
try {
|
||||
final results = await _searchCities(event.query);
|
||||
final List<String> results = await _searchCities(event.query);
|
||||
emit(state.copyWith(locationSuggestions: results));
|
||||
} catch (e) {
|
||||
// Quietly fail or clear
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
emit(state.copyWith(locationSuggestions: <String>[]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
ProfileSetupClearLocationSuggestions event,
|
||||
Emitter<ProfileSetupState> emit,
|
||||
) {
|
||||
emit(state.copyWith(locationSuggestions: []));
|
||||
emit(state.copyWith(locationSuggestions: <String>[]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ abstract class ProfileSetupEvent extends Equatable {
|
||||
|
||||
/// Event triggered when the full name changes.
|
||||
class ProfileSetupFullNameChanged extends ProfileSetupEvent {
|
||||
/// The new full name value.
|
||||
final String fullName;
|
||||
|
||||
/// Creates a [ProfileSetupFullNameChanged] event.
|
||||
const ProfileSetupFullNameChanged(this.fullName);
|
||||
/// The new full name value.
|
||||
final String fullName;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[fullName];
|
||||
@@ -22,11 +22,11 @@ class ProfileSetupFullNameChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the bio changes.
|
||||
class ProfileSetupBioChanged extends ProfileSetupEvent {
|
||||
/// The new bio value.
|
||||
final String bio;
|
||||
|
||||
/// Creates a [ProfileSetupBioChanged] event.
|
||||
const ProfileSetupBioChanged(this.bio);
|
||||
/// The new bio value.
|
||||
final String bio;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[bio];
|
||||
@@ -34,11 +34,11 @@ class ProfileSetupBioChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the preferred locations change.
|
||||
class ProfileSetupLocationsChanged extends ProfileSetupEvent {
|
||||
/// The new list of locations.
|
||||
final List<String> locations;
|
||||
|
||||
/// Creates a [ProfileSetupLocationsChanged] event.
|
||||
const ProfileSetupLocationsChanged(this.locations);
|
||||
/// The new list of locations.
|
||||
final List<String> locations;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[locations];
|
||||
@@ -46,11 +46,11 @@ class ProfileSetupLocationsChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the max distance changes.
|
||||
class ProfileSetupDistanceChanged extends ProfileSetupEvent {
|
||||
/// The new max distance value in miles.
|
||||
final double distance;
|
||||
|
||||
/// Creates a [ProfileSetupDistanceChanged] event.
|
||||
const ProfileSetupDistanceChanged(this.distance);
|
||||
/// The new max distance value in miles.
|
||||
final double distance;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[distance];
|
||||
@@ -58,11 +58,11 @@ class ProfileSetupDistanceChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the skills change.
|
||||
class ProfileSetupSkillsChanged extends ProfileSetupEvent {
|
||||
/// The new list of selected skills.
|
||||
final List<String> skills;
|
||||
|
||||
/// Creates a [ProfileSetupSkillsChanged] event.
|
||||
const ProfileSetupSkillsChanged(this.skills);
|
||||
/// The new list of selected skills.
|
||||
final List<String> skills;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[skills];
|
||||
@@ -70,11 +70,11 @@ class ProfileSetupSkillsChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the industries change.
|
||||
class ProfileSetupIndustriesChanged extends ProfileSetupEvent {
|
||||
/// The new list of selected industries.
|
||||
final List<String> industries;
|
||||
|
||||
/// Creates a [ProfileSetupIndustriesChanged] event.
|
||||
const ProfileSetupIndustriesChanged(this.industries);
|
||||
/// The new list of selected industries.
|
||||
final List<String> industries;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[industries];
|
||||
@@ -82,11 +82,11 @@ class ProfileSetupIndustriesChanged extends ProfileSetupEvent {
|
||||
|
||||
/// Event triggered when the location query changes.
|
||||
class ProfileSetupLocationQueryChanged extends ProfileSetupEvent {
|
||||
/// The search query.
|
||||
final String query;
|
||||
|
||||
/// Creates a [ProfileSetupLocationQueryChanged] event.
|
||||
const ProfileSetupLocationQueryChanged(this.query);
|
||||
/// The search query.
|
||||
final String query;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[query];
|
||||
|
||||
@@ -5,6 +5,19 @@ enum ProfileSetupStatus { initial, loading, success, failure }
|
||||
|
||||
/// State for the ProfileSetupBloc.
|
||||
class ProfileSetupState extends Equatable {
|
||||
|
||||
/// Creates a [ProfileSetupState] instance.
|
||||
const ProfileSetupState({
|
||||
this.fullName = '',
|
||||
this.bio = '',
|
||||
this.preferredLocations = const <String>[],
|
||||
this.maxDistanceMiles = 25,
|
||||
this.skills = const <String>[],
|
||||
this.industries = const <String>[],
|
||||
this.status = ProfileSetupStatus.initial,
|
||||
this.errorMessage,
|
||||
this.locationSuggestions = const <String>[],
|
||||
});
|
||||
/// The user's full name.
|
||||
final String fullName;
|
||||
|
||||
@@ -32,19 +45,6 @@ class ProfileSetupState extends Equatable {
|
||||
/// List of location suggestions from the API.
|
||||
final List<String> locationSuggestions;
|
||||
|
||||
/// Creates a [ProfileSetupState] instance.
|
||||
const ProfileSetupState({
|
||||
this.fullName = '',
|
||||
this.bio = '',
|
||||
this.preferredLocations = const <String>[],
|
||||
this.maxDistanceMiles = 25,
|
||||
this.skills = const <String>[],
|
||||
this.industries = const <String>[],
|
||||
this.status = ProfileSetupStatus.initial,
|
||||
this.errorMessage,
|
||||
this.locationSuggestions = const <String>[],
|
||||
});
|
||||
|
||||
/// Creates a copy of the current state with updated values.
|
||||
ProfileSetupState copyWith({
|
||||
String? fullName,
|
||||
|
||||
@@ -17,11 +17,11 @@ import '../widgets/phone_verification_page/phone_input.dart';
|
||||
/// This page coordinates the authentication flow by switching between
|
||||
/// [PhoneInput] and [OtpVerification] based on the current [AuthState].
|
||||
class PhoneVerificationPage extends StatefulWidget {
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
/// Creates a [PhoneVerificationPage].
|
||||
const PhoneVerificationPage({super.key, required this.mode});
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
@override
|
||||
State<PhoneVerificationPage> createState() => _PhoneVerificationPageState();
|
||||
|
||||
@@ -157,9 +157,9 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
|
||||
),
|
||||
),
|
||||
child: isCreatingProfile
|
||||
? ElevatedButton(
|
||||
? const ElevatedButton(
|
||||
onPressed: null,
|
||||
child: const SizedBox(
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
|
||||
@@ -3,17 +3,17 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget for displaying a section title and subtitle
|
||||
class SectionTitleSubtitle extends StatelessWidget {
|
||||
/// The title of the section
|
||||
final String title;
|
||||
|
||||
/// The subtitle of the section
|
||||
final String subtitle;
|
||||
|
||||
const SectionTitleSubtitle({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
});
|
||||
/// The title of the section
|
||||
final String title;
|
||||
|
||||
/// The subtitle of the section
|
||||
final String subtitle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -3,14 +3,14 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
|
||||
class GetStartedActions extends StatelessWidget {
|
||||
final VoidCallback onSignUpPressed;
|
||||
final VoidCallback onLoginPressed;
|
||||
|
||||
const GetStartedActions({
|
||||
super.key,
|
||||
required this.onSignUpPressed,
|
||||
required this.onLoginPressed,
|
||||
});
|
||||
final VoidCallback onSignUpPressed;
|
||||
final VoidCallback onLoginPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Column(
|
||||
children: [
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
// Logo
|
||||
Image.asset(
|
||||
@@ -35,7 +35,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
child: ClipOval(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
// Layer 1: The Fallback Logo (Always visible until image loads)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space12),
|
||||
@@ -47,7 +47,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
Image.network(
|
||||
'https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=400&h=400&fit=crop&crop=faces',
|
||||
fit: BoxFit.cover,
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded) return child;
|
||||
// Only animate opacity if we have a frame
|
||||
return AnimatedOpacity(
|
||||
@@ -56,12 +56,12 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||
// While loading, show nothing (transparent) so layer 1 shows
|
||||
if (loadingProgress == null) return child;
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
|
||||
// On error, show nothing (transparent) so layer 1 shows
|
||||
// Also schedule a state update to prevent retries if needed
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -83,7 +83,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
// Pagination dots (Visual only)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: UiConstants.space6,
|
||||
height: UiConstants.space2,
|
||||
|
||||
@@ -8,6 +8,15 @@ import 'otp_verification/otp_verification_header.dart';
|
||||
|
||||
/// A widget that displays the OTP verification UI.
|
||||
class OtpVerification extends StatelessWidget {
|
||||
|
||||
/// Creates an [OtpVerification].
|
||||
const OtpVerification({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.onOtpSubmitted,
|
||||
required this.onResend,
|
||||
required this.onContinue,
|
||||
});
|
||||
/// The current state of the authentication process.
|
||||
final AuthState state;
|
||||
|
||||
@@ -20,15 +29,6 @@ class OtpVerification extends StatelessWidget {
|
||||
/// Callback for the "Continue" action.
|
||||
final VoidCallback onContinue;
|
||||
|
||||
/// Creates an [OtpVerification].
|
||||
const OtpVerification({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.onOtpSubmitted,
|
||||
required this.onResend,
|
||||
required this.onContinue,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
|
||||
@@ -11,11 +11,6 @@ import '../../../blocs/auth_bloc.dart';
|
||||
/// This widget handles its own internal [TextEditingController]s and focus nodes.
|
||||
/// It dispatches [AuthOtpUpdated] to the [AuthBloc] on every change.
|
||||
class OtpInputField extends StatefulWidget {
|
||||
/// Callback for when the OTP code is fully entered (6 digits).
|
||||
final ValueChanged<String> onCompleted;
|
||||
|
||||
/// The error message to display, if any.
|
||||
final String error;
|
||||
|
||||
/// Creates an [OtpInputField].
|
||||
const OtpInputField({
|
||||
@@ -23,6 +18,11 @@ class OtpInputField extends StatefulWidget {
|
||||
required this.onCompleted,
|
||||
required this.error,
|
||||
});
|
||||
/// Callback for when the OTP code is fully entered (6 digits).
|
||||
final ValueChanged<String> onCompleted;
|
||||
|
||||
/// The error message to display, if any.
|
||||
final String error;
|
||||
|
||||
@override
|
||||
State<OtpInputField> createState() => _OtpInputFieldState();
|
||||
|
||||
@@ -4,11 +4,6 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget that handles the OTP resend logic and countdown timer.
|
||||
class OtpResendSection extends StatefulWidget {
|
||||
/// Callback for when the resend link is pressed.
|
||||
final VoidCallback onResend;
|
||||
|
||||
/// Whether an error is currently displayed. (Used for layout tweaks in the original code)
|
||||
final bool hasError;
|
||||
|
||||
/// Creates an [OtpResendSection].
|
||||
const OtpResendSection({
|
||||
@@ -16,6 +11,11 @@ class OtpResendSection extends StatefulWidget {
|
||||
required this.onResend,
|
||||
this.hasError = false,
|
||||
});
|
||||
/// Callback for when the resend link is pressed.
|
||||
final VoidCallback onResend;
|
||||
|
||||
/// Whether an error is currently displayed. (Used for layout tweaks in the original code)
|
||||
final bool hasError;
|
||||
|
||||
@override
|
||||
State<OtpResendSection> createState() => _OtpResendSectionState();
|
||||
|
||||
@@ -6,14 +6,6 @@ import '../../common/auth_trouble_link.dart';
|
||||
|
||||
/// A widget that displays the primary action button and trouble link for OTP verification.
|
||||
class OtpVerificationActions extends StatelessWidget {
|
||||
/// Whether the verification process is currently loading.
|
||||
final bool isLoading;
|
||||
|
||||
/// Whether the submit button should be enabled.
|
||||
final bool canSubmit;
|
||||
|
||||
/// Callback for when the Continue button is pressed.
|
||||
final VoidCallback? onContinue;
|
||||
|
||||
/// Creates an [OtpVerificationActions].
|
||||
const OtpVerificationActions({
|
||||
@@ -22,6 +14,14 @@ class OtpVerificationActions extends StatelessWidget {
|
||||
required this.canSubmit,
|
||||
this.onContinue,
|
||||
});
|
||||
/// Whether the verification process is currently loading.
|
||||
final bool isLoading;
|
||||
|
||||
/// Whether the submit button should be enabled.
|
||||
final bool canSubmit;
|
||||
|
||||
/// Callback for when the Continue button is pressed.
|
||||
final VoidCallback? onContinue;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -36,9 +36,9 @@ class OtpVerificationActions extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
isLoading
|
||||
? ElevatedButton(
|
||||
? const ElevatedButton(
|
||||
onPressed: null,
|
||||
child: const SizedBox(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget that displays the title and subtitle for the OTP Verification page.
|
||||
class OtpVerificationHeader extends StatelessWidget {
|
||||
/// The phone number to which the code was sent.
|
||||
final String phoneNumber;
|
||||
|
||||
/// Creates an [OtpVerificationHeader].
|
||||
const OtpVerificationHeader({super.key, required this.phoneNumber});
|
||||
/// The phone number to which the code was sent.
|
||||
final String phoneNumber;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -5,11 +5,6 @@ import 'package:staff_authentication/src/presentation/widgets/common/auth_troubl
|
||||
|
||||
/// A widget that displays the primary action button and trouble link for Phone Input.
|
||||
class PhoneInputActions extends StatelessWidget {
|
||||
/// Whether the sign-in process is currently loading.
|
||||
final bool isLoading;
|
||||
|
||||
/// Callback for when the Send Code button is pressed.
|
||||
final VoidCallback? onSendCode;
|
||||
|
||||
/// Creates a [PhoneInputActions].
|
||||
const PhoneInputActions({
|
||||
@@ -17,6 +12,11 @@ class PhoneInputActions extends StatelessWidget {
|
||||
required this.isLoading,
|
||||
this.onSendCode,
|
||||
});
|
||||
/// Whether the sign-in process is currently loading.
|
||||
final bool isLoading;
|
||||
|
||||
/// Callback for when the Send Code button is pressed.
|
||||
final VoidCallback? onSendCode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -29,9 +29,9 @@ class PhoneInputActions extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
isLoading
|
||||
? UiButton.secondary(
|
||||
? const UiButton.secondary(
|
||||
onPressed: null,
|
||||
child: const SizedBox(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
|
||||
@@ -2,20 +2,11 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart';
|
||||
|
||||
/// A widget that displays the phone number input field with country code.
|
||||
///
|
||||
/// This widget handles its own [TextEditingController] to manage input.
|
||||
class PhoneInputFormField extends StatefulWidget {
|
||||
/// The initial value for the phone number.
|
||||
final String initialValue;
|
||||
|
||||
/// The error message to display, if any.
|
||||
final String error;
|
||||
|
||||
/// Callback for when the text field value changes.
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
/// Creates a [PhoneInputFormField].
|
||||
const PhoneInputFormField({
|
||||
@@ -24,6 +15,14 @@ class PhoneInputFormField extends StatefulWidget {
|
||||
required this.error,
|
||||
required this.onChanged,
|
||||
});
|
||||
/// The initial value for the phone number.
|
||||
final String initialValue;
|
||||
|
||||
/// The error message to display, if any.
|
||||
final String error;
|
||||
|
||||
/// Callback for when the text field value changes.
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
@override
|
||||
State<PhoneInputFormField> createState() => _PhoneInputFormFieldState();
|
||||
|
||||
@@ -5,6 +5,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
|
||||
|
||||
/// A widget for setting up basic profile information (photo, name, bio).
|
||||
class ProfileSetupBasicInfo extends StatelessWidget {
|
||||
|
||||
/// Creates a [ProfileSetupBasicInfo] widget.
|
||||
const ProfileSetupBasicInfo({
|
||||
super.key,
|
||||
required this.fullName,
|
||||
required this.bio,
|
||||
required this.onFullNameChanged,
|
||||
required this.onBioChanged,
|
||||
});
|
||||
/// The user's full name.
|
||||
final String fullName;
|
||||
|
||||
@@ -17,15 +26,6 @@ class ProfileSetupBasicInfo extends StatelessWidget {
|
||||
/// Callback for when the bio changes.
|
||||
final ValueChanged<String> onBioChanged;
|
||||
|
||||
/// Creates a [ProfileSetupBasicInfo] widget.
|
||||
const ProfileSetupBasicInfo({
|
||||
super.key,
|
||||
required this.fullName,
|
||||
required this.bio,
|
||||
required this.onFullNameChanged,
|
||||
required this.onBioChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
/// Builds the basic info step UI.
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -6,6 +6,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
|
||||
|
||||
/// A widget for setting up skills and preferred industries.
|
||||
class ProfileSetupExperience extends StatelessWidget {
|
||||
|
||||
/// Creates a [ProfileSetupExperience] widget.
|
||||
const ProfileSetupExperience({
|
||||
super.key,
|
||||
required this.skills,
|
||||
required this.industries,
|
||||
required this.onSkillsChanged,
|
||||
required this.onIndustriesChanged,
|
||||
});
|
||||
/// The list of selected skills.
|
||||
final List<String> skills;
|
||||
|
||||
@@ -18,15 +27,6 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
/// Callback for when industries change.
|
||||
final ValueChanged<List<String>> onIndustriesChanged;
|
||||
|
||||
/// Creates a [ProfileSetupExperience] widget.
|
||||
const ProfileSetupExperience({
|
||||
super.key,
|
||||
required this.skills,
|
||||
required this.industries,
|
||||
required this.onSkillsChanged,
|
||||
required this.onIndustriesChanged,
|
||||
});
|
||||
|
||||
/// Toggles a skill.
|
||||
void _toggleSkill({required String skill}) {
|
||||
final List<String> updatedList = List<String>.from(skills);
|
||||
|
||||
@@ -4,14 +4,6 @@ import 'package:flutter/material.dart';
|
||||
|
||||
/// A header widget for the profile setup page showing back button and step count.
|
||||
class ProfileSetupHeader extends StatelessWidget {
|
||||
/// The current step index (0-based).
|
||||
final int currentStep;
|
||||
|
||||
/// The total number of steps.
|
||||
final int totalSteps;
|
||||
|
||||
/// Callback when the back button is tapped.
|
||||
final VoidCallback? onBackTap;
|
||||
|
||||
/// Creates a [ProfileSetupHeader].
|
||||
const ProfileSetupHeader({
|
||||
@@ -20,6 +12,14 @@ class ProfileSetupHeader extends StatelessWidget {
|
||||
required this.totalSteps,
|
||||
this.onBackTap,
|
||||
});
|
||||
/// The current step index (0-based).
|
||||
final int currentStep;
|
||||
|
||||
/// The total number of steps.
|
||||
final int totalSteps;
|
||||
|
||||
/// Callback when the back button is tapped.
|
||||
final VoidCallback? onBackTap;
|
||||
|
||||
@override
|
||||
/// Builds the header UI.
|
||||
|
||||
@@ -9,6 +9,15 @@ import 'package:staff_authentication/src/presentation/widgets/common/section_tit
|
||||
|
||||
/// A widget for setting up preferred work locations and distance.
|
||||
class ProfileSetupLocation extends StatefulWidget {
|
||||
|
||||
/// Creates a [ProfileSetupLocation] widget.
|
||||
const ProfileSetupLocation({
|
||||
super.key,
|
||||
required this.preferredLocations,
|
||||
required this.maxDistanceMiles,
|
||||
required this.onLocationsChanged,
|
||||
required this.onDistanceChanged,
|
||||
});
|
||||
/// The list of preferred locations.
|
||||
final List<String> preferredLocations;
|
||||
|
||||
@@ -21,15 +30,6 @@ class ProfileSetupLocation extends StatefulWidget {
|
||||
/// Callback for when the max distance changes.
|
||||
final ValueChanged<double> onDistanceChanged;
|
||||
|
||||
/// Creates a [ProfileSetupLocation] widget.
|
||||
const ProfileSetupLocation({
|
||||
super.key,
|
||||
required this.preferredLocations,
|
||||
required this.maxDistanceMiles,
|
||||
required this.onLocationsChanged,
|
||||
required this.onDistanceChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ProfileSetupLocation> createState() => _ProfileSetupLocationState();
|
||||
}
|
||||
@@ -97,9 +97,9 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
|
||||
// Suggestions List
|
||||
BlocBuilder<ProfileSetupBloc, ProfileSetupState>(
|
||||
buildWhen: (previous, current) =>
|
||||
buildWhen: (ProfileSetupState previous, ProfileSetupState current) =>
|
||||
previous.locationSuggestions != current.locationSuggestions,
|
||||
builder: (context, state) {
|
||||
builder: (BuildContext context, ProfileSetupState state) {
|
||||
if (state.locationSuggestions.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -114,9 +114,9 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: state.locationSuggestions.length,
|
||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = state.locationSuggestions[index];
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(height: 1),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final String suggestion = state.locationSuggestions[index];
|
||||
return ListTile(
|
||||
title: Text(suggestion, style: UiTypography.body2m),
|
||||
leading: const Icon(UiIcons.mapPin, size: 16),
|
||||
|
||||
@@ -31,7 +31,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
|
||||
) async {
|
||||
emit(AvailabilityLoading());
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final days = await getWeeklyAvailability(
|
||||
GetWeeklyAvailabilityParams(event.weekStart),
|
||||
@@ -103,7 +103,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
|
||||
));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
@@ -155,7 +155,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
|
||||
));
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await updateDayAvailability(UpdateDayAvailabilityParams(newDay));
|
||||
// Success feedback
|
||||
@@ -195,7 +195,7 @@ class AvailabilityBloc extends Bloc<AvailabilityEvent, AvailabilityState>
|
||||
);
|
||||
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final newDays = await applyQuickSet(
|
||||
ApplyQuickSetParams(currentState.currentWeekStart, event.type),
|
||||
|
||||
@@ -404,7 +404,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
value: isAvailable,
|
||||
onChanged: (val) =>
|
||||
context.read<AvailabilityBloc>().add(ToggleDayStatus(day)),
|
||||
activeColor: UiColors.primary,
|
||||
activeThumbColor: UiColors.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -417,7 +417,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
|
||||
final uiConfig = _getSlotUiConfig(slot.id);
|
||||
|
||||
return _buildTimeSlotItem(context, day, slot, uiConfig);
|
||||
}).toList(),
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
library staff_availability;
|
||||
library;
|
||||
|
||||
export 'src/staff_availability_module.dart';
|
||||
|
||||
@@ -49,7 +49,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.loading));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final List<Shift> shifts = await _getTodaysShift();
|
||||
final AttendanceStatus status = await _getAttendanceStatus();
|
||||
@@ -88,7 +88,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
||||
Emitter<ClockInState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
@@ -203,7 +203,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.actionInProgress));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final AttendanceStatus newStatus = await _clockIn(
|
||||
ClockInArguments(shiftId: event.shiftId, notes: event.notes),
|
||||
@@ -226,7 +226,7 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState>
|
||||
) async {
|
||||
emit(state.copyWith(status: ClockInStatus.actionInProgress));
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
final AttendanceStatus newStatus = await _clockOut(
|
||||
ClockOutArguments(
|
||||
|
||||
@@ -32,7 +32,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = Translations.of(context).staff.clock_in;
|
||||
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
|
||||
return BlocProvider<ClockInBloc>.value(
|
||||
value: _bloc,
|
||||
child: BlocConsumer<ClockInBloc, ClockInState>(
|
||||
@@ -479,7 +479,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
}
|
||||
|
||||
Future<void> _showNFCDialog(BuildContext context) async {
|
||||
final i18n = Translations.of(context).staff.clock_in;
|
||||
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
|
||||
bool scanned = false;
|
||||
|
||||
// Using a local navigator context since we are in a dialog
|
||||
@@ -622,7 +622,7 @@ class _ClockInPageState extends State<ClockInPage> {
|
||||
final DateTime windowStart = shiftStart.subtract(const Duration(minutes: 15));
|
||||
return DateFormat('h:mm a').format(windowStart);
|
||||
} catch (e) {
|
||||
final i18n = Translations.of(context).staff.clock_in;
|
||||
final TranslationsStaffClockInEn i18n = Translations.of(context).staff.clock_in;
|
||||
return i18n.soon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final CommuteMode mode = _getAppMode();
|
||||
final i18n = Translations.of(context).staff.clock_in.commute;
|
||||
final TranslationsStaffClockInCommuteEn i18n = Translations.of(context).staff.clock_in.commute;
|
||||
|
||||
// Notify parent of mode change
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -501,7 +501,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space5),
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: <Color>[
|
||||
|
||||
@@ -39,7 +39,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = Translations.of(context).staff.clock_in.lunch_break;
|
||||
final TranslationsStaffClockInLunchBreakEn i18n = Translations.of(context).staff.clock_in.lunch_break;
|
||||
return Dialog(
|
||||
backgroundColor: UiColors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -171,7 +171,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String>(
|
||||
isExpanded: true,
|
||||
value: _breakStart,
|
||||
initialValue: _breakStart,
|
||||
items: _timeOptions
|
||||
.map(
|
||||
(String t) => DropdownMenuItem(
|
||||
@@ -194,7 +194,7 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String>(
|
||||
isExpanded: true,
|
||||
value: _breakEnd,
|
||||
initialValue: _breakEnd,
|
||||
items: _timeOptions
|
||||
.map(
|
||||
(String t) => DropdownMenuItem(
|
||||
|
||||
@@ -72,7 +72,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = Translations.of(context).staff.clock_in.swipe;
|
||||
final TranslationsStaffClockInSwipeEn i18n = Translations.of(context).staff.clock_in.swipe;
|
||||
final Color baseColor = widget.isCheckedIn
|
||||
? UiColors.success
|
||||
: UiColors.primary;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user