fix: add ignore_for_file to data connect Repos and modify CI to avoid analyzing deleted files

This commit is contained in:
2026-02-20 19:51:44 +05:30
parent 24835f127e
commit 474be43448
259 changed files with 1810 additions and 1714 deletions

View File

@@ -150,7 +150,7 @@ jobs:
FAILED_FILES=() FAILED_FILES=()
while IFS= read -r file; do while IFS= read -r file; do
if [[ -n "$file" && "$file" == *.dart ]]; then if [[ -n "$file" && "$file" == *.dart && -f "$file" ]]; then
echo "📝 Analyzing: $file" echo "📝 Analyzing: $file"
if ! flutter analyze "$file" --no-fatal-infos 2>&1 | tee -a lint_output.txt; then if ! flutter analyze "$file" --no-fatal-infos 2>&1 | tee -a lint_output.txt; then

View File

@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that listens to session state changes and handles global reactions. /// A widget that listens to session state changes and handles global reactions.
/// ///

View File

@@ -1,4 +1,4 @@
library core; library;
export 'src/domain/arguments/usecase_argument.dart'; export 'src/domain/arguments/usecase_argument.dart';
export 'src/domain/usecases/usecase.dart'; export 'src/domain/usecases/usecase.dart';

View File

@@ -22,16 +22,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
/// } /// }
/// ``` /// ```
class CoreBlocObserver extends BlocObserver { class CoreBlocObserver extends BlocObserver {
/// Whether to log state changes (can be verbose in production)
final bool logStateChanges;
/// Whether to log events
final bool logEvents;
CoreBlocObserver({ CoreBlocObserver({
this.logStateChanges = false, this.logStateChanges = false,
this.logEvents = true, this.logEvents = true,
}); });
/// Whether to log state changes (can be verbose in production)
final bool logStateChanges;
/// Whether to log events
final bool logEvents;
@override @override
void onCreate(BlocBase bloc) { void onCreate(BlocBase bloc) {

View File

@@ -41,6 +41,7 @@
/// final homePath = ClientPaths.home; /// final homePath = ClientPaths.home;
/// final shiftsPath = StaffPaths.shifts; /// final shiftsPath = StaffPaths.shifts;
/// ``` /// ```
library;
export 'client/route_paths.dart'; export 'client/route_paths.dart';
export 'client/navigator.dart'; export 'client/navigator.dart';

View File

@@ -8,11 +8,11 @@ sealed class LocaleEvent {
/// Event triggered when the user wants to change the application locale. /// Event triggered when the user wants to change the application locale.
class ChangeLocale extends LocaleEvent { class ChangeLocale extends LocaleEvent {
/// The new locale to apply.
final Locale locale;
/// Creates a [ChangeLocale] event. /// Creates a [ChangeLocale] event.
const ChangeLocale(this.locale); const ChangeLocale(this.locale);
/// The new locale to apply.
final Locale locale;
} }
/// Event triggered to load the saved locale from persistent storage. /// Event triggered to load the saved locale from persistent storage.

View File

@@ -11,11 +11,11 @@ abstract interface class LocaleLocalDataSource {
/// Implementation of [LocaleLocalDataSource] using [SharedPreferencesAsync]. /// Implementation of [LocaleLocalDataSource] using [SharedPreferencesAsync].
class LocaleLocalDataSourceImpl implements LocaleLocalDataSource { class LocaleLocalDataSourceImpl implements LocaleLocalDataSource {
static const String _localeKey = 'app_locale';
final SharedPreferencesAsync _sharedPreferences;
/// Creates a [LocaleLocalDataSourceImpl] with the required [SharedPreferencesAsync] instance. /// Creates a [LocaleLocalDataSourceImpl] with the required [SharedPreferencesAsync] instance.
LocaleLocalDataSourceImpl(this._sharedPreferences); LocaleLocalDataSourceImpl(this._sharedPreferences);
static const String _localeKey = 'app_locale';
final SharedPreferencesAsync _sharedPreferences;
@override @override
Future<void> saveLanguageCode(String languageCode) async { Future<void> saveLanguageCode(String languageCode) async {

View File

@@ -3,10 +3,10 @@ import '../repositories/locale_repository_interface.dart';
/// Use case to retrieve the default locale. /// Use case to retrieve the default locale.
class GetDefaultLocaleUseCase { class GetDefaultLocaleUseCase {
final LocaleRepositoryInterface _repository;
/// Creates a [GetDefaultLocaleUseCase] with the required [LocaleRepositoryInterface]. /// Creates a [GetDefaultLocaleUseCase] with the required [LocaleRepositoryInterface].
GetDefaultLocaleUseCase(this._repository); GetDefaultLocaleUseCase(this._repository);
final LocaleRepositoryInterface _repository;
/// Retrieves the default locale. /// Retrieves the default locale.
Locale call() { Locale call() {

View File

@@ -7,10 +7,10 @@ import '../repositories/locale_repository_interface.dart';
/// This class extends [NoInputUseCase] and interacts with [LocaleRepositoryInterface] /// This class extends [NoInputUseCase] and interacts with [LocaleRepositoryInterface]
/// to fetch the saved locale. /// to fetch the saved locale.
class GetLocaleUseCase extends NoInputUseCase<Locale?> { class GetLocaleUseCase extends NoInputUseCase<Locale?> {
final LocaleRepositoryInterface _repository;
/// Creates a [GetLocaleUseCase] with the required [LocaleRepositoryInterface]. /// Creates a [GetLocaleUseCase] with the required [LocaleRepositoryInterface].
GetLocaleUseCase(this._repository); GetLocaleUseCase(this._repository);
final LocaleRepositoryInterface _repository;
@override @override
Future<Locale> call() { Future<Locale> call() {

View File

@@ -3,10 +3,10 @@ import '../repositories/locale_repository_interface.dart';
/// Use case to retrieve the list of supported locales. /// Use case to retrieve the list of supported locales.
class GetSupportedLocalesUseCase { class GetSupportedLocalesUseCase {
final LocaleRepositoryInterface _repository;
/// Creates a [GetSupportedLocalesUseCase] with the required [LocaleRepositoryInterface]. /// Creates a [GetSupportedLocalesUseCase] with the required [LocaleRepositoryInterface].
GetSupportedLocalesUseCase(this._repository); GetSupportedLocalesUseCase(this._repository);
final LocaleRepositoryInterface _repository;
/// Retrieves the supported locales. /// Retrieves the supported locales.
List<Locale> call() { List<Locale> call() {

View File

@@ -7,10 +7,10 @@ import '../repositories/locale_repository_interface.dart';
/// This class extends [UseCase] and interacts with [LocaleRepositoryInterface] /// This class extends [UseCase] and interacts with [LocaleRepositoryInterface]
/// to save a given locale. /// to save a given locale.
class SetLocaleUseCase extends UseCase<Locale, void> { class SetLocaleUseCase extends UseCase<Locale, void> {
final LocaleRepositoryInterface _repository;
/// Creates a [SetLocaleUseCase] with the required [LocaleRepositoryInterface]. /// Creates a [SetLocaleUseCase] with the required [LocaleRepositoryInterface].
SetLocaleUseCase(this._repository); SetLocaleUseCase(this._repository);
final LocaleRepositoryInterface _repository;
@override @override
Future<void> call(Locale input) { Future<void> call(Locale input) {

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/billing_connector_repository.dart'; import '../../domain/repositories/billing_connector_repository.dart';
@@ -13,7 +15,7 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
@override @override
Future<List<BusinessBankAccount>> getBankAccounts({required String businessId}) async { Future<List<BusinessBankAccount>> getBankAccounts({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final result = await _service.connector final QueryResult<dc.GetAccountsByOwnerIdData, dc.GetAccountsByOwnerIdVariables> result = await _service.connector
.getAccountsByOwnerId(ownerId: businessId) .getAccountsByOwnerId(ownerId: businessId)
.execute(); .execute();
@@ -24,21 +26,21 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
@override @override
Future<double> getCurrentBillAmount({required String businessId}) async { Future<double> getCurrentBillAmount({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final result = await _service.connector final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
.listInvoicesByBusinessId(businessId: businessId) .listInvoicesByBusinessId(businessId: businessId)
.execute(); .execute();
return result.data.invoices return result.data.invoices
.map(_mapInvoice) .map(_mapInvoice)
.where((i) => i.status == InvoiceStatus.open) .where((Invoice i) => i.status == InvoiceStatus.open)
.fold<double>(0.0, (sum, item) => sum + item.totalAmount); .fold<double>(0.0, (double sum, Invoice item) => sum + item.totalAmount);
}); });
} }
@override @override
Future<List<Invoice>> getInvoiceHistory({required String businessId}) async { Future<List<Invoice>> getInvoiceHistory({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final result = await _service.connector final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
.listInvoicesByBusinessId(businessId: businessId) .listInvoicesByBusinessId(businessId: businessId)
.limit(10) .limit(10)
.execute(); .execute();
@@ -50,13 +52,13 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
@override @override
Future<List<Invoice>> getPendingInvoices({required String businessId}) async { Future<List<Invoice>> getPendingInvoices({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final result = await _service.connector final QueryResult<dc.ListInvoicesByBusinessIdData, dc.ListInvoicesByBusinessIdVariables> result = await _service.connector
.listInvoicesByBusinessId(businessId: businessId) .listInvoicesByBusinessId(businessId: businessId)
.execute(); .execute();
return result.data.invoices return result.data.invoices
.map(_mapInvoice) .map(_mapInvoice)
.where((i) => .where((Invoice i) =>
i.status == InvoiceStatus.open || i.status == InvoiceStatus.disputed) i.status == InvoiceStatus.open || i.status == InvoiceStatus.disputed)
.toList(); .toList();
}); });
@@ -83,7 +85,7 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
end = DateTime(now.year, now.month + 1, 0, 23, 59, 59); end = DateTime(now.year, now.month + 1, 0, 23, 59, 59);
} }
final result = await _service.connector final QueryResult<dc.ListShiftRolesByBusinessAndDatesSummaryData, dc.ListShiftRolesByBusinessAndDatesSummaryVariables> result = await _service.connector
.listShiftRolesByBusinessAndDatesSummary( .listShiftRolesByBusinessAndDatesSummary(
businessId: businessId, businessId: businessId,
start: _service.toTimestamp(start), start: _service.toTimestamp(start),
@@ -91,17 +93,17 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
) )
.execute(); .execute();
final shiftRoles = result.data.shiftRoles; final List<dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles> shiftRoles = result.data.shiftRoles;
if (shiftRoles.isEmpty) return []; if (shiftRoles.isEmpty) return <InvoiceItem>[];
final Map<String, _RoleSummary> summary = {}; final Map<String, _RoleSummary> summary = <String, _RoleSummary>{};
for (final role in shiftRoles) { for (final dc.ListShiftRolesByBusinessAndDatesSummaryShiftRoles role in shiftRoles) {
final roleId = role.roleId; final String roleId = role.roleId;
final roleName = role.role.name; final String roleName = role.role.name;
final hours = role.hours ?? 0.0; final double hours = role.hours ?? 0.0;
final totalValue = role.totalValue ?? 0.0; final double totalValue = role.totalValue ?? 0.0;
final existing = summary[roleId]; final _RoleSummary? existing = summary[roleId];
if (existing == null) { if (existing == null) {
summary[roleId] = _RoleSummary( summary[roleId] = _RoleSummary(
roleId: roleId, roleId: roleId,
@@ -118,7 +120,7 @@ class BillingConnectorRepositoryImpl implements BillingConnectorRepository {
} }
return summary.values return summary.values
.map((item) => InvoiceItem( .map((_RoleSummary item) => InvoiceItem(
id: item.roleId, id: item.roleId,
invoiceId: item.roleId, invoiceId: item.roleId,
staffId: item.roleName, staffId: item.roleName,
@@ -197,3 +199,4 @@ class _RoleSummary {
); );
} }
} }

View File

@@ -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:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -20,7 +22,7 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
final DateTime start = DateTime(date.year, date.month, date.day); final DateTime start = DateTime(date.year, date.month, date.day);
final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999); final DateTime end = DateTime(date.year, date.month, date.day, 23, 59, 59, 999);
final shiftRolesResult = await _service.connector final QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData, dc.ListShiftRolesByBusinessAndDateRangeVariables> shiftRolesResult = await _service.connector
.listShiftRolesByBusinessAndDateRange( .listShiftRolesByBusinessAndDateRange(
businessId: businessId, businessId: businessId,
start: _service.toTimestamp(start), start: _service.toTimestamp(start),
@@ -28,7 +30,7 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
) )
.execute(); .execute();
final applicationsResult = await _service.connector final QueryResult<dc.ListStaffsApplicationsByBusinessForDayData, dc.ListStaffsApplicationsByBusinessForDayVariables> applicationsResult = await _service.connector
.listStaffsApplicationsByBusinessForDay( .listStaffsApplicationsByBusinessForDay(
businessId: businessId, businessId: businessId,
dayStart: _service.toTimestamp(start), dayStart: _service.toTimestamp(start),
@@ -49,13 +51,13 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
List<dynamic> applications, List<dynamic> applications,
DateTime date, DateTime date,
) { ) {
if (shiftRoles.isEmpty && applications.isEmpty) return []; if (shiftRoles.isEmpty && applications.isEmpty) return <CoverageShift>[];
final Map<String, _CoverageGroup> groups = {}; final Map<String, _CoverageGroup> groups = <String, _CoverageGroup>{};
for (final sr in shiftRoles) { for (final sr in shiftRoles) {
final String key = '${sr.shiftId}:${sr.roleId}'; final String key = '${sr.shiftId}:${sr.roleId}';
final startTime = _service.toDateTime(sr.startTime); final DateTime? startTime = _service.toDateTime(sr.startTime);
groups[key] = _CoverageGroup( groups[key] = _CoverageGroup(
shiftId: sr.shiftId, shiftId: sr.shiftId,
@@ -65,14 +67,14 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '00:00', startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '00:00',
workersNeeded: sr.count, workersNeeded: sr.count,
date: _service.toDateTime(sr.shift.date) ?? date, date: _service.toDateTime(sr.shift.date) ?? date,
workers: [], workers: <CoverageWorker>[],
); );
} }
for (final app in applications) { for (final app in applications) {
final String key = '${app.shiftId}:${app.roleId}'; final String key = '${app.shiftId}:${app.roleId}';
if (!groups.containsKey(key)) { if (!groups.containsKey(key)) {
final startTime = _service.toDateTime(app.shiftRole.startTime); final DateTime? startTime = _service.toDateTime(app.shiftRole.startTime);
groups[key] = _CoverageGroup( groups[key] = _CoverageGroup(
shiftId: app.shiftId, shiftId: app.shiftId,
roleId: app.roleId, roleId: app.roleId,
@@ -81,11 +83,11 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '00:00', startTime: startTime != null ? DateFormat('HH:mm').format(startTime) : '00:00',
workersNeeded: app.shiftRole.count, workersNeeded: app.shiftRole.count,
date: _service.toDateTime(app.shiftRole.shift.date) ?? date, date: _service.toDateTime(app.shiftRole.shift.date) ?? date,
workers: [], workers: <CoverageWorker>[],
); );
} }
final checkIn = _service.toDateTime(app.checkInTime); final DateTime? checkIn = _service.toDateTime(app.checkInTime);
groups[key]!.workers.add( groups[key]!.workers.add(
CoverageWorker( CoverageWorker(
name: app.staff.fullName, name: app.staff.fullName,
@@ -96,7 +98,7 @@ class CoverageConnectorRepositoryImpl implements CoverageConnectorRepository {
} }
return groups.values return groups.values
.map((g) => CoverageShift( .map((_CoverageGroup g) => CoverageShift(
id: '${g.shiftId}:${g.roleId}', id: '${g.shiftId}:${g.roleId}',
title: g.title, title: g.title,
location: g.location, location: g.location,
@@ -153,3 +155,4 @@ class _CoverageGroup {
final DateTime date; final DateTime date;
final List<CoverageWorker> workers; final List<CoverageWorker> workers;
} }

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/home_connector_repository.dart'; import '../../domain/repositories/home_connector_repository.dart';
@@ -13,13 +15,13 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
@override @override
Future<HomeDashboardData> getDashboardData({required String businessId}) async { Future<HomeDashboardData> getDashboardData({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final now = DateTime.now(); final DateTime now = DateTime.now();
final daysFromMonday = now.weekday - DateTime.monday; final int daysFromMonday = now.weekday - DateTime.monday;
final monday = DateTime(now.year, now.month, now.day).subtract(Duration(days: daysFromMonday)); final DateTime monday = DateTime(now.year, now.month, now.day).subtract(Duration(days: daysFromMonday));
final weekRangeStart = monday; final DateTime weekRangeStart = monday;
final weekRangeEnd = monday.add(const Duration(days: 13, hours: 23, minutes: 59, seconds: 59)); final DateTime weekRangeEnd = monday.add(const Duration(days: 13, hours: 23, minutes: 59, seconds: 59));
final completedResult = await _service.connector final QueryResult<dc.GetCompletedShiftsByBusinessIdData, dc.GetCompletedShiftsByBusinessIdVariables> completedResult = await _service.connector
.getCompletedShiftsByBusinessId( .getCompletedShiftsByBusinessId(
businessId: businessId, businessId: businessId,
dateFrom: _service.toTimestamp(weekRangeStart), dateFrom: _service.toTimestamp(weekRangeStart),
@@ -32,14 +34,14 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
int weeklyShifts = 0; int weeklyShifts = 0;
int next7DaysScheduled = 0; int next7DaysScheduled = 0;
for (final shift in completedResult.data.shifts) { for (final dc.GetCompletedShiftsByBusinessIdShifts shift in completedResult.data.shifts) {
final shiftDate = _service.toDateTime(shift.date); final DateTime? shiftDate = _service.toDateTime(shift.date);
if (shiftDate == null) continue; if (shiftDate == null) continue;
final offset = shiftDate.difference(weekRangeStart).inDays; final int offset = shiftDate.difference(weekRangeStart).inDays;
if (offset < 0 || offset > 13) continue; if (offset < 0 || offset > 13) continue;
final cost = shift.cost ?? 0.0; final double cost = shift.cost ?? 0.0;
if (offset <= 6) { if (offset <= 6) {
weeklySpending += cost; weeklySpending += cost;
weeklyShifts += 1; weeklyShifts += 1;
@@ -49,10 +51,10 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
} }
} }
final start = DateTime(now.year, now.month, now.day); final DateTime start = DateTime(now.year, now.month, now.day);
final end = start.add(const Duration(hours: 23, minutes: 59, seconds: 59)); final DateTime end = start.add(const Duration(hours: 23, minutes: 59, seconds: 59));
final result = await _service.connector final QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData, dc.ListShiftRolesByBusinessAndDateRangeVariables> result = await _service.connector
.listShiftRolesByBusinessAndDateRange( .listShiftRolesByBusinessAndDateRange(
businessId: businessId, businessId: businessId,
start: _service.toTimestamp(start), start: _service.toTimestamp(start),
@@ -62,7 +64,7 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
int totalNeeded = 0; int totalNeeded = 0;
int totalFilled = 0; int totalFilled = 0;
for (final shiftRole in result.data.shiftRoles) { for (final dc.ListShiftRolesByBusinessAndDateRangeShiftRoles shiftRole in result.data.shiftRoles) {
totalNeeded += shiftRole.count; totalNeeded += shiftRole.count;
totalFilled += shiftRole.assigned ?? 0; totalFilled += shiftRole.assigned ?? 0;
} }
@@ -81,10 +83,10 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
@override @override
Future<List<ReorderItem>> getRecentReorders({required String businessId}) async { Future<List<ReorderItem>> getRecentReorders({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final now = DateTime.now(); final DateTime now = DateTime.now();
final start = now.subtract(const Duration(days: 30)); final DateTime start = now.subtract(const Duration(days: 30));
final result = await _service.connector final QueryResult<dc.ListShiftRolesByBusinessDateRangeCompletedOrdersData, dc.ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> result = await _service.connector
.listShiftRolesByBusinessDateRangeCompletedOrders( .listShiftRolesByBusinessDateRangeCompletedOrders(
businessId: businessId, businessId: businessId,
start: _service.toTimestamp(start), start: _service.toTimestamp(start),
@@ -92,7 +94,7 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
) )
.execute(); .execute();
return result.data.shiftRoles.map((shiftRole) { return result.data.shiftRoles.map((dc.ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole) {
final String location = shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? ''; final String location = shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? '';
final String type = shiftRole.shift.order.orderType.stringValue; final String type = shiftRole.shift.order.orderType.stringValue;
return ReorderItem( return ReorderItem(
@@ -108,3 +110,4 @@ class HomeConnectorRepositoryImpl implements HomeConnectorRepository {
}); });
} }
} }

View File

@@ -1,4 +1,6 @@
// 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 'dart:convert'; import 'dart:convert';
import 'package:firebase_data_connect/src/core/ref.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -17,11 +19,11 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
Future<List<Hub>> getHubs({required String businessId}) async { Future<List<Hub>> getHubs({required String businessId}) async {
return _service.run(() async { return _service.run(() async {
final String teamId = await _getOrCreateTeamId(businessId); final String teamId = await _getOrCreateTeamId(businessId);
final response = await _service.connector final QueryResult<dc.GetTeamHubsByTeamIdData, dc.GetTeamHubsByTeamIdVariables> response = await _service.connector
.getTeamHubsByTeamId(teamId: teamId) .getTeamHubsByTeamId(teamId: teamId)
.execute(); .execute();
return response.data.teamHubs.map((h) { return response.data.teamHubs.map((dc.GetTeamHubsByTeamIdTeamHubs h) {
return Hub( return Hub(
id: h.id, id: h.id,
businessId: businessId, businessId: businessId,
@@ -54,7 +56,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
? await _fetchPlaceAddress(placeId) ? await _fetchPlaceAddress(placeId)
: null; : null;
final result = await _service.connector final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> result = await _service.connector
.createTeamHub( .createTeamHub(
teamId: teamId, teamId: teamId,
hubName: name, hubName: name,
@@ -101,7 +103,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
? await _fetchPlaceAddress(placeId) ? await _fetchPlaceAddress(placeId)
: null; : null;
final builder = _service.connector.updateTeamHub(id: id); final dc.UpdateTeamHubVariablesBuilder builder = _service.connector.updateTeamHub(id: id);
if (name != null) builder.hubName(name); if (name != null) builder.hubName(name);
if (address != null) builder.address(address); if (address != null) builder.address(address);
@@ -141,7 +143,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
@override @override
Future<void> deleteHub({required String businessId, required String id}) async { Future<void> deleteHub({required String businessId, required String id}) async {
return _service.run(() async { return _service.run(() async {
final ordersRes = await _service.connector final QueryResult<dc.ListOrdersByBusinessAndTeamHubData, dc.ListOrdersByBusinessAndTeamHubVariables> ordersRes = await _service.connector
.listOrdersByBusinessAndTeamHub(businessId: businessId, teamHubId: id) .listOrdersByBusinessAndTeamHub(businessId: businessId, teamHubId: id)
.execute(); .execute();
@@ -158,7 +160,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
// --- HELPERS --- // --- HELPERS ---
Future<String> _getOrCreateTeamId(String businessId) async { Future<String> _getOrCreateTeamId(String businessId) async {
final teamsRes = await _service.connector final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables> teamsRes = await _service.connector
.getTeamsByOwnerId(ownerId: businessId) .getTeamsByOwnerId(ownerId: businessId)
.execute(); .execute();
@@ -168,7 +170,7 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
// Logic to fetch business details to create a team name if missing // Logic to fetch business details to create a team name if missing
// For simplicity, we assume one exists or we create a generic one // For simplicity, we assume one exists or we create a generic one
final createRes = await _service.connector final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables> createRes = await _service.connector
.createTeam( .createTeam(
teamName: 'Business Team', teamName: 'Business Team',
ownerId: businessId, ownerId: businessId,
@@ -184,30 +186,30 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
final Uri uri = Uri.https( final Uri uri = Uri.https(
'maps.googleapis.com', 'maps.googleapis.com',
'/maps/api/place/details/json', '/maps/api/place/details/json',
{ <String, dynamic>{
'place_id': placeId, 'place_id': placeId,
'fields': 'address_component', 'fields': 'address_component',
'key': AppConfig.googleMapsApiKey, 'key': AppConfig.googleMapsApiKey,
}, },
); );
try { try {
final response = await http.get(uri); final http.Response response = await http.get(uri);
if (response.statusCode != 200) return null; if (response.statusCode != 200) return null;
final payload = json.decode(response.body) as Map<String, dynamic>; final Map<String, dynamic> payload = json.decode(response.body) as Map<String, dynamic>;
if (payload['status'] != 'OK') return null; if (payload['status'] != 'OK') return null;
final result = payload['result'] as Map<String, dynamic>?; final Map<String, dynamic>? result = payload['result'] as Map<String, dynamic>?;
final components = result?['address_components'] as List<dynamic>?; final List<dynamic>? components = result?['address_components'] as List<dynamic>?;
if (components == null || components.isEmpty) return null; if (components == null || components.isEmpty) return null;
String? streetNumber, route, city, state, country, zipCode; String? streetNumber, route, city, state, country, zipCode;
for (var entry in components) { for (var entry in components) {
final component = entry as Map<String, dynamic>; final Map<String, dynamic> component = entry as Map<String, dynamic>;
final types = component['types'] as List<dynamic>? ?? []; final List<dynamic> types = component['types'] as List<dynamic>? ?? <dynamic>[];
final longName = component['long_name'] as String?; final String? longName = component['long_name'] as String?;
final shortName = component['short_name'] as String?; final String? shortName = component['short_name'] as String?;
if (types.contains('street_number')) { if (types.contains('street_number')) {
streetNumber = longName; streetNumber = longName;
@@ -224,8 +226,8 @@ class HubsConnectorRepositoryImpl implements HubsConnectorRepository {
} }
} }
final street = [streetNumber, route] final String street = <String?>[streetNumber, route]
.where((v) => v != null && v.isNotEmpty) .where((String? v) => v != null && v.isNotEmpty)
.join(' ') .join(' ')
.trim(); .trim();
@@ -257,3 +259,4 @@ class _PlaceAddress {
final String? country; final String? country;
final String? zipCode; final String? zipCode;
} }

View File

@@ -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:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -21,25 +22,25 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final response = await _service.connector final QueryResult<dc.ListShiftsForDailyOpsByBusinessData, dc.ListShiftsForDailyOpsByBusinessVariables> response = await _service.connector
.listShiftsForDailyOpsByBusiness( .listShiftsForDailyOpsByBusiness(
businessId: id, businessId: id,
date: _service.toTimestamp(date), date: _service.toTimestamp(date),
) )
.execute(); .execute();
final shifts = response.data.shifts; final List<dc.ListShiftsForDailyOpsByBusinessShifts> shifts = response.data.shifts;
int scheduledShifts = shifts.length; final int scheduledShifts = shifts.length;
int workersConfirmed = 0; int workersConfirmed = 0;
int inProgressShifts = 0; int inProgressShifts = 0;
int completedShifts = 0; int completedShifts = 0;
final List<DailyOpsShift> dailyOpsShifts = []; final List<DailyOpsShift> dailyOpsShifts = <DailyOpsShift>[];
for (final shift in shifts) { for (final dc.ListShiftsForDailyOpsByBusinessShifts shift in shifts) {
workersConfirmed += shift.filled ?? 0; workersConfirmed += shift.filled ?? 0;
final statusStr = shift.status?.stringValue ?? ''; final String statusStr = shift.status?.stringValue ?? '';
if (statusStr == 'IN_PROGRESS') inProgressShifts++; if (statusStr == 'IN_PROGRESS') inProgressShifts++;
if (statusStr == 'COMPLETED') completedShifts++; if (statusStr == 'COMPLETED') completedShifts++;
@@ -73,7 +74,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final response = await _service.connector final QueryResult<dc.ListInvoicesForSpendByBusinessData, dc.ListInvoicesForSpendByBusinessVariables> response = await _service.connector
.listInvoicesForSpendByBusiness( .listInvoicesForSpendByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -81,22 +82,22 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final invoices = response.data.invoices; final List<dc.ListInvoicesForSpendByBusinessInvoices> invoices = response.data.invoices;
double totalSpend = 0.0; double totalSpend = 0.0;
int paidInvoices = 0; int paidInvoices = 0;
int pendingInvoices = 0; int pendingInvoices = 0;
int overdueInvoices = 0; int overdueInvoices = 0;
final List<SpendInvoice> spendInvoices = []; final List<SpendInvoice> spendInvoices = <SpendInvoice>[];
final Map<DateTime, double> dailyAggregates = {}; final Map<DateTime, double> dailyAggregates = <DateTime, double>{};
final Map<String, double> industryAggregates = {}; final Map<String, double> industryAggregates = <String, double>{};
for (final inv in invoices) { for (final dc.ListInvoicesForSpendByBusinessInvoices inv in invoices) {
final amount = (inv.amount ?? 0.0).toDouble(); final double amount = (inv.amount ?? 0.0).toDouble();
totalSpend += amount; totalSpend += amount;
final statusStr = inv.status.stringValue; final String statusStr = inv.status.stringValue;
if (statusStr == 'PAID') { if (statusStr == 'PAID') {
paidInvoices++; paidInvoices++;
} else if (statusStr == 'PENDING') { } else if (statusStr == 'PENDING') {
@@ -105,49 +106,49 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
overdueInvoices++; overdueInvoices++;
} }
final industry = inv.vendor?.serviceSpecialty ?? 'Other'; final String industry = inv.vendor.serviceSpecialty ?? 'Other';
industryAggregates[industry] = (industryAggregates[industry] ?? 0.0) + amount; industryAggregates[industry] = (industryAggregates[industry] ?? 0.0) + amount;
final issueDateTime = inv.issueDate.toDateTime(); final DateTime issueDateTime = inv.issueDate.toDateTime();
spendInvoices.add(SpendInvoice( spendInvoices.add(SpendInvoice(
id: inv.id, id: inv.id,
invoiceNumber: inv.invoiceNumber ?? '', invoiceNumber: inv.invoiceNumber ?? '',
issueDate: issueDateTime, issueDate: issueDateTime,
amount: amount, amount: amount,
status: statusStr, status: statusStr,
vendorName: inv.vendor?.companyName ?? 'Unknown', vendorName: inv.vendor.companyName ?? 'Unknown',
industry: industry, industry: industry,
)); ));
// Chart data aggregation // Chart data aggregation
final date = DateTime(issueDateTime.year, issueDateTime.month, issueDateTime.day); final DateTime date = DateTime(issueDateTime.year, issueDateTime.month, issueDateTime.day);
dailyAggregates[date] = (dailyAggregates[date] ?? 0.0) + amount; dailyAggregates[date] = (dailyAggregates[date] ?? 0.0) + amount;
} }
// Ensure chart data covers all days in range // Ensure chart data covers all days in range
final Map<DateTime, double> completeDailyAggregates = {}; final Map<DateTime, double> completeDailyAggregates = <DateTime, double>{};
for (int i = 0; i <= endDate.difference(startDate).inDays; i++) { for (int i = 0; i <= endDate.difference(startDate).inDays; i++) {
final date = startDate.add(Duration(days: i)); final DateTime date = startDate.add(Duration(days: i));
final normalizedDate = DateTime(date.year, date.month, date.day); final DateTime normalizedDate = DateTime(date.year, date.month, date.day);
completeDailyAggregates[normalizedDate] = completeDailyAggregates[normalizedDate] =
dailyAggregates[normalizedDate] ?? 0.0; dailyAggregates[normalizedDate] ?? 0.0;
} }
final List<SpendChartPoint> chartData = completeDailyAggregates.entries final List<SpendChartPoint> chartData = completeDailyAggregates.entries
.map((e) => SpendChartPoint(date: e.key, amount: e.value)) .map((MapEntry<DateTime, double> e) => SpendChartPoint(date: e.key, amount: e.value))
.toList() .toList()
..sort((a, b) => a.date.compareTo(b.date)); ..sort((SpendChartPoint a, SpendChartPoint b) => a.date.compareTo(b.date));
final List<SpendIndustryCategory> industryBreakdown = industryAggregates.entries final List<SpendIndustryCategory> industryBreakdown = industryAggregates.entries
.map((e) => SpendIndustryCategory( .map((MapEntry<String, double> e) => SpendIndustryCategory(
name: e.key, name: e.key,
amount: e.value, amount: e.value,
percentage: totalSpend > 0 ? (e.value / totalSpend * 100) : 0, percentage: totalSpend > 0 ? (e.value / totalSpend * 100) : 0,
)) ))
.toList() .toList()
..sort((a, b) => b.amount.compareTo(a.amount)); ..sort((SpendIndustryCategory a, SpendIndustryCategory b) => b.amount.compareTo(a.amount));
final daysCount = endDate.difference(startDate).inDays + 1; final int daysCount = endDate.difference(startDate).inDays + 1;
return SpendReport( return SpendReport(
totalSpend: totalSpend, totalSpend: totalSpend,
@@ -170,7 +171,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final response = await _service.connector final QueryResult<dc.ListShiftsForCoverageData, dc.ListShiftsForCoverageVariables> response = await _service.connector
.listShiftsForCoverage( .listShiftsForCoverage(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -178,36 +179,36 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final shifts = response.data.shifts; final List<dc.ListShiftsForCoverageShifts> shifts = response.data.shifts;
int totalNeeded = 0; int totalNeeded = 0;
int totalFilled = 0; int totalFilled = 0;
final Map<DateTime, (int, int)> dailyStats = {}; final Map<DateTime, (int, int)> dailyStats = <DateTime, (int, int)>{};
for (final shift in shifts) { for (final dc.ListShiftsForCoverageShifts shift in shifts) {
final shiftDate = shift.date?.toDateTime() ?? DateTime.now(); final DateTime shiftDate = shift.date?.toDateTime() ?? DateTime.now();
final date = DateTime(shiftDate.year, shiftDate.month, shiftDate.day); final DateTime date = DateTime(shiftDate.year, shiftDate.month, shiftDate.day);
final needed = shift.workersNeeded ?? 0; final int needed = shift.workersNeeded ?? 0;
final filled = shift.filled ?? 0; final int filled = shift.filled ?? 0;
totalNeeded += needed; totalNeeded += needed;
totalFilled += filled; totalFilled += filled;
final current = dailyStats[date] ?? (0, 0); final (int, int) current = dailyStats[date] ?? (0, 0);
dailyStats[date] = (current.$1 + needed, current.$2 + filled); dailyStats[date] = (current.$1 + needed, current.$2 + filled);
} }
final List<CoverageDay> dailyCoverage = dailyStats.entries.map((e) { final List<CoverageDay> dailyCoverage = dailyStats.entries.map((MapEntry<DateTime, (int, int)> e) {
final needed = e.value.$1; final int needed = e.value.$1;
final filled = e.value.$2; final int filled = e.value.$2;
return CoverageDay( return CoverageDay(
date: e.key, date: e.key,
needed: needed, needed: needed,
filled: filled, filled: filled,
percentage: needed == 0 ? 100.0 : (filled / needed) * 100.0, percentage: needed == 0 ? 100.0 : (filled / needed) * 100.0,
); );
}).toList()..sort((a, b) => a.date.compareTo(b.date)); }).toList()..sort((CoverageDay a, CoverageDay b) => a.date.compareTo(b.date));
return CoverageReport( return CoverageReport(
overallCoverage: totalNeeded == 0 ? 100.0 : (totalFilled / totalNeeded) * 100.0, overallCoverage: totalNeeded == 0 ? 100.0 : (totalFilled / totalNeeded) * 100.0,
@@ -226,7 +227,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final response = await _service.connector final QueryResult<dc.ListShiftsForForecastByBusinessData, dc.ListShiftsForForecastByBusinessVariables> response = await _service.connector
.listShiftsForForecastByBusiness( .listShiftsForForecastByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -234,43 +235,43 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final shifts = response.data.shifts; final List<dc.ListShiftsForForecastByBusinessShifts> shifts = response.data.shifts;
double projectedSpend = 0.0; double projectedSpend = 0.0;
int projectedWorkers = 0; int projectedWorkers = 0;
double totalHours = 0.0; double totalHours = 0.0;
final Map<DateTime, (double, int)> dailyStats = {}; final Map<DateTime, (double, int)> dailyStats = <DateTime, (double, int)>{};
// Weekly stats: index -> (cost, count, hours) // Weekly stats: index -> (cost, count, hours)
final Map<int, (double, int, double)> weeklyStats = { final Map<int, (double, int, double)> weeklyStats = <int, (double, int, double)>{
0: (0.0, 0, 0.0), 0: (0.0, 0, 0.0),
1: (0.0, 0, 0.0), 1: (0.0, 0, 0.0),
2: (0.0, 0, 0.0), 2: (0.0, 0, 0.0),
3: (0.0, 0, 0.0), 3: (0.0, 0, 0.0),
}; };
for (final shift in shifts) { for (final dc.ListShiftsForForecastByBusinessShifts shift in shifts) {
final shiftDate = shift.date?.toDateTime() ?? DateTime.now(); final DateTime shiftDate = shift.date?.toDateTime() ?? DateTime.now();
final date = DateTime(shiftDate.year, shiftDate.month, shiftDate.day); final DateTime date = DateTime(shiftDate.year, shiftDate.month, shiftDate.day);
final cost = (shift.cost ?? 0.0).toDouble(); final double cost = (shift.cost ?? 0.0).toDouble();
final workers = shift.workersNeeded ?? 0; final int workers = shift.workersNeeded ?? 0;
final hoursVal = (shift.hours ?? 0).toDouble(); final double hoursVal = (shift.hours ?? 0).toDouble();
final shiftTotalHours = hoursVal * workers; final double shiftTotalHours = hoursVal * workers;
projectedSpend += cost; projectedSpend += cost;
projectedWorkers += workers; projectedWorkers += workers;
totalHours += shiftTotalHours; totalHours += shiftTotalHours;
final current = dailyStats[date] ?? (0.0, 0); final (double, int) current = dailyStats[date] ?? (0.0, 0);
dailyStats[date] = (current.$1 + cost, current.$2 + workers); dailyStats[date] = (current.$1 + cost, current.$2 + workers);
// Weekly logic // Weekly logic
final diffDays = shiftDate.difference(startDate).inDays; final int diffDays = shiftDate.difference(startDate).inDays;
if (diffDays >= 0) { if (diffDays >= 0) {
final weekIndex = diffDays ~/ 7; final int weekIndex = diffDays ~/ 7;
if (weekIndex < 4) { if (weekIndex < 4) {
final wCurrent = weeklyStats[weekIndex]!; final (double, int, double) wCurrent = weeklyStats[weekIndex]!;
weeklyStats[weekIndex] = ( weeklyStats[weekIndex] = (
wCurrent.$1 + cost, wCurrent.$1 + cost,
wCurrent.$2 + 1, wCurrent.$2 + 1,
@@ -280,17 +281,17 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
} }
} }
final List<ForecastPoint> chartData = dailyStats.entries.map((e) { final List<ForecastPoint> chartData = dailyStats.entries.map((MapEntry<DateTime, (double, int)> e) {
return ForecastPoint( return ForecastPoint(
date: e.key, date: e.key,
projectedCost: e.value.$1, projectedCost: e.value.$1,
workersNeeded: e.value.$2, workersNeeded: e.value.$2,
); );
}).toList()..sort((a, b) => a.date.compareTo(b.date)); }).toList()..sort((ForecastPoint a, ForecastPoint b) => a.date.compareTo(b.date));
final List<ForecastWeek> weeklyBreakdown = []; final List<ForecastWeek> weeklyBreakdown = <ForecastWeek>[];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
final stats = weeklyStats[i]!; final (double, int, double) stats = weeklyStats[i]!;
weeklyBreakdown.add(ForecastWeek( weeklyBreakdown.add(ForecastWeek(
weekNumber: i + 1, weekNumber: i + 1,
totalCost: stats.$1, totalCost: stats.$1,
@@ -300,8 +301,8 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
)); ));
} }
final weeksCount = (endDate.difference(startDate).inDays / 7).ceil(); final int weeksCount = (endDate.difference(startDate).inDays / 7).ceil();
final avgWeeklySpend = weeksCount > 0 ? projectedSpend / weeksCount : 0.0; final double avgWeeklySpend = weeksCount > 0 ? projectedSpend / weeksCount : 0.0;
return ForecastReport( return ForecastReport(
projectedSpend: projectedSpend, projectedSpend: projectedSpend,
@@ -324,7 +325,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final response = await _service.connector final QueryResult<dc.ListShiftsForPerformanceByBusinessData, dc.ListShiftsForPerformanceByBusinessVariables> response = await _service.connector
.listShiftsForPerformanceByBusiness( .listShiftsForPerformanceByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -332,7 +333,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final shifts = response.data.shifts; final List<dc.ListShiftsForPerformanceByBusinessShifts> shifts = response.data.shifts;
int totalNeeded = 0; int totalNeeded = 0;
int totalFilled = 0; int totalFilled = 0;
@@ -340,7 +341,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
double totalFillTimeSeconds = 0.0; double totalFillTimeSeconds = 0.0;
int filledShiftsWithTime = 0; int filledShiftsWithTime = 0;
for (final shift in shifts) { for (final dc.ListShiftsForPerformanceByBusinessShifts shift in shifts) {
totalNeeded += shift.workersNeeded ?? 0; totalNeeded += shift.workersNeeded ?? 0;
totalFilled += shift.filled ?? 0; totalFilled += shift.filled ?? 0;
if ((shift.status?.stringValue ?? '') == 'COMPLETED') { if ((shift.status?.stringValue ?? '') == 'COMPLETED') {
@@ -348,8 +349,8 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
} }
if (shift.filledAt != null && shift.createdAt != null) { if (shift.filledAt != null && shift.createdAt != null) {
final createdAt = shift.createdAt!.toDateTime(); final DateTime createdAt = shift.createdAt!.toDateTime();
final filledAt = shift.filledAt!.toDateTime(); final DateTime filledAt = shift.filledAt!.toDateTime();
totalFillTimeSeconds += filledAt.difference(createdAt).inSeconds; totalFillTimeSeconds += filledAt.difference(createdAt).inSeconds;
filledShiftsWithTime++; filledShiftsWithTime++;
} }
@@ -366,7 +367,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
completionRate: completionRate, completionRate: completionRate,
onTimeRate: 95.0, onTimeRate: 95.0,
avgFillTimeHours: avgFillTimeHours, avgFillTimeHours: avgFillTimeHours,
keyPerformanceIndicators: [ keyPerformanceIndicators: <PerformanceMetric>[
PerformanceMetric(label: 'Fill Rate', value: '${fillRate.toStringAsFixed(1)}%', trend: 0.02), PerformanceMetric(label: 'Fill Rate', value: '${fillRate.toStringAsFixed(1)}%', trend: 0.02),
PerformanceMetric(label: 'Completion', value: '${completionRate.toStringAsFixed(1)}%', trend: 0.05), PerformanceMetric(label: 'Completion', value: '${completionRate.toStringAsFixed(1)}%', trend: 0.05),
PerformanceMetric(label: 'Avg Fill Time', value: '${avgFillTimeHours.toStringAsFixed(1)}h', trend: -0.1), PerformanceMetric(label: 'Avg Fill Time', value: '${avgFillTimeHours.toStringAsFixed(1)}h', trend: -0.1),
@@ -384,7 +385,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
return _service.run(() async { return _service.run(() async {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
final shiftsResponse = await _service.connector final QueryResult<dc.ListShiftsForNoShowRangeByBusinessData, dc.ListShiftsForNoShowRangeByBusinessVariables> shiftsResponse = await _service.connector
.listShiftsForNoShowRangeByBusiness( .listShiftsForNoShowRangeByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -392,34 +393,34 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final shiftIds = shiftsResponse.data.shifts.map((s) => s.id).toList(); final List<String> shiftIds = shiftsResponse.data.shifts.map((dc.ListShiftsForNoShowRangeByBusinessShifts s) => s.id).toList();
if (shiftIds.isEmpty) { if (shiftIds.isEmpty) {
return const NoShowReport(totalNoShows: 0, noShowRate: 0, flaggedWorkers: []); return const NoShowReport(totalNoShows: 0, noShowRate: 0, flaggedWorkers: <NoShowWorker>[]);
} }
final appsResponse = await _service.connector final QueryResult<dc.ListApplicationsForNoShowRangeData, dc.ListApplicationsForNoShowRangeVariables> appsResponse = await _service.connector
.listApplicationsForNoShowRange(shiftIds: shiftIds) .listApplicationsForNoShowRange(shiftIds: shiftIds)
.execute(); .execute();
final apps = appsResponse.data.applications; final List<dc.ListApplicationsForNoShowRangeApplications> apps = appsResponse.data.applications;
final noShowApps = apps.where((a) => (a.status.stringValue) == 'NO_SHOW').toList(); final List<dc.ListApplicationsForNoShowRangeApplications> noShowApps = apps.where((dc.ListApplicationsForNoShowRangeApplications a) => (a.status.stringValue) == 'NO_SHOW').toList();
final noShowStaffIds = noShowApps.map((a) => a.staffId).toSet().toList(); final List<String> noShowStaffIds = noShowApps.map((dc.ListApplicationsForNoShowRangeApplications a) => a.staffId).toSet().toList();
if (noShowStaffIds.isEmpty) { if (noShowStaffIds.isEmpty) {
return NoShowReport( return NoShowReport(
totalNoShows: noShowApps.length, totalNoShows: noShowApps.length,
noShowRate: apps.isEmpty ? 0 : (noShowApps.length / apps.length) * 100.0, noShowRate: apps.isEmpty ? 0 : (noShowApps.length / apps.length) * 100.0,
flaggedWorkers: [], flaggedWorkers: <NoShowWorker>[],
); );
} }
final staffResponse = await _service.connector final QueryResult<dc.ListStaffForNoShowReportData, dc.ListStaffForNoShowReportVariables> staffResponse = await _service.connector
.listStaffForNoShowReport(staffIds: noShowStaffIds) .listStaffForNoShowReport(staffIds: noShowStaffIds)
.execute(); .execute();
final staffList = staffResponse.data.staffs; final List<dc.ListStaffForNoShowReportStaffs> staffList = staffResponse.data.staffs;
final List<NoShowWorker> flaggedWorkers = staffList.map((s) => NoShowWorker( final List<NoShowWorker> flaggedWorkers = staffList.map((dc.ListStaffForNoShowReportStaffs s) => NoShowWorker(
id: s.id, id: s.id,
fullName: s.fullName ?? '', fullName: s.fullName ?? '',
noShowCount: s.noShowCount ?? 0, noShowCount: s.noShowCount ?? 0,
@@ -444,7 +445,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
final String id = businessId ?? await _service.getBusinessId(); final String id = businessId ?? await _service.getBusinessId();
// Use forecast query for hours/cost data // Use forecast query for hours/cost data
final shiftsResponse = await _service.connector final QueryResult<dc.ListShiftsForForecastByBusinessData, dc.ListShiftsForForecastByBusinessVariables> shiftsResponse = await _service.connector
.listShiftsForForecastByBusiness( .listShiftsForForecastByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -453,7 +454,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
.execute(); .execute();
// Use performance query for avgFillTime (has filledAt + createdAt) // Use performance query for avgFillTime (has filledAt + createdAt)
final perfResponse = await _service.connector final QueryResult<dc.ListShiftsForPerformanceByBusinessData, dc.ListShiftsForPerformanceByBusinessVariables> perfResponse = await _service.connector
.listShiftsForPerformanceByBusiness( .listShiftsForPerformanceByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -461,7 +462,7 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final invoicesResponse = await _service.connector final QueryResult<dc.ListInvoicesForSpendByBusinessData, dc.ListInvoicesForSpendByBusinessVariables> invoicesResponse = await _service.connector
.listInvoicesForSpendByBusiness( .listInvoicesForSpendByBusiness(
businessId: id, businessId: id,
startDate: _service.toTimestamp(startDate), startDate: _service.toTimestamp(startDate),
@@ -469,15 +470,15 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
) )
.execute(); .execute();
final forecastShifts = shiftsResponse.data.shifts; final List<dc.ListShiftsForForecastByBusinessShifts> forecastShifts = shiftsResponse.data.shifts;
final perfShifts = perfResponse.data.shifts; final List<dc.ListShiftsForPerformanceByBusinessShifts> perfShifts = perfResponse.data.shifts;
final invoices = invoicesResponse.data.invoices; final List<dc.ListInvoicesForSpendByBusinessInvoices> invoices = invoicesResponse.data.invoices;
// Aggregate hours and fill rate from forecast shifts // Aggregate hours and fill rate from forecast shifts
double totalHours = 0; double totalHours = 0;
int totalNeeded = 0; int totalNeeded = 0;
for (final shift in forecastShifts) { for (final dc.ListShiftsForForecastByBusinessShifts shift in forecastShifts) {
totalHours += (shift.hours ?? 0).toDouble(); totalHours += (shift.hours ?? 0).toDouble();
totalNeeded += shift.workersNeeded ?? 0; totalNeeded += shift.workersNeeded ?? 0;
} }
@@ -488,13 +489,13 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
double totalFillTimeSeconds = 0; double totalFillTimeSeconds = 0;
int filledShiftsWithTime = 0; int filledShiftsWithTime = 0;
for (final shift in perfShifts) { for (final dc.ListShiftsForPerformanceByBusinessShifts shift in perfShifts) {
perfNeeded += shift.workersNeeded ?? 0; perfNeeded += shift.workersNeeded ?? 0;
perfFilled += shift.filled ?? 0; perfFilled += shift.filled ?? 0;
if (shift.filledAt != null && shift.createdAt != null) { if (shift.filledAt != null && shift.createdAt != null) {
final createdAt = shift.createdAt!.toDateTime(); final DateTime createdAt = shift.createdAt!.toDateTime();
final filledAt = shift.filledAt!.toDateTime(); final DateTime filledAt = shift.filledAt!.toDateTime();
totalFillTimeSeconds += filledAt.difference(createdAt).inSeconds; totalFillTimeSeconds += filledAt.difference(createdAt).inSeconds;
filledShiftsWithTime++; filledShiftsWithTime++;
} }
@@ -502,19 +503,19 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
// Aggregate total spend from invoices // Aggregate total spend from invoices
double totalSpend = 0; double totalSpend = 0;
for (final inv in invoices) { for (final dc.ListInvoicesForSpendByBusinessInvoices inv in invoices) {
totalSpend += (inv.amount ?? 0).toDouble(); totalSpend += (inv.amount ?? 0).toDouble();
} }
// Fetch no-show rate using forecast shift IDs // Fetch no-show rate using forecast shift IDs
final shiftIds = forecastShifts.map((s) => s.id).toList(); final List<String> shiftIds = forecastShifts.map((dc.ListShiftsForForecastByBusinessShifts s) => s.id).toList();
double noShowRate = 0; double noShowRate = 0;
if (shiftIds.isNotEmpty) { if (shiftIds.isNotEmpty) {
final appsResponse = await _service.connector final QueryResult<dc.ListApplicationsForNoShowRangeData, dc.ListApplicationsForNoShowRangeVariables> appsResponse = await _service.connector
.listApplicationsForNoShowRange(shiftIds: shiftIds) .listApplicationsForNoShowRange(shiftIds: shiftIds)
.execute(); .execute();
final apps = appsResponse.data.applications; final List<dc.ListApplicationsForNoShowRangeApplications> apps = appsResponse.data.applications;
final noShowApps = apps.where((a) => (a.status.stringValue) == 'NO_SHOW').toList(); final List<dc.ListApplicationsForNoShowRangeApplications> noShowApps = apps.where((dc.ListApplicationsForNoShowRangeApplications a) => (a.status.stringValue) == 'NO_SHOW').toList();
noShowRate = apps.isEmpty ? 0 : (noShowApps.length / apps.length) * 100.0; noShowRate = apps.isEmpty ? 0 : (noShowApps.length / apps.length) * 100.0;
} }
@@ -533,3 +534,4 @@ class ReportsConnectorRepositoryImpl implements ReportsConnectorRepository {
}); });
} }
} }

View File

@@ -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:firebase_data_connect/firebase_data_connect.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_data_connect/krow_data_connect.dart' as dc;
@@ -22,12 +23,12 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
required DateTime end, required DateTime end,
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final query = _service.connector final dc.GetApplicationsByStaffIdVariablesBuilder query = _service.connector
.getApplicationsByStaffId(staffId: staffId) .getApplicationsByStaffId(staffId: staffId)
.dayStart(_service.toTimestamp(start)) .dayStart(_service.toTimestamp(start))
.dayEnd(_service.toTimestamp(end)); .dayEnd(_service.toTimestamp(end));
final response = await query.execute(); final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> response = await query.execute();
return _mapApplicationsToShifts(response.data.applications); return _mapApplicationsToShifts(response.data.applications);
}); });
} }
@@ -42,29 +43,29 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
// First, fetch all available shift roles for the vendor/business // First, fetch all available shift roles for the vendor/business
// Use the session owner ID (vendorId) // Use the session owner ID (vendorId)
final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId; final String? vendorId = dc.StaffSessionStore.instance.session?.ownerId;
if (vendorId == null || vendorId.isEmpty) return []; if (vendorId == null || vendorId.isEmpty) return <Shift>[];
final response = await _service.connector final QueryResult<dc.ListShiftRolesByVendorIdData, dc.ListShiftRolesByVendorIdVariables> response = await _service.connector
.listShiftRolesByVendorId(vendorId: vendorId) .listShiftRolesByVendorId(vendorId: vendorId)
.execute(); .execute();
final allShiftRoles = response.data.shiftRoles; final List<dc.ListShiftRolesByVendorIdShiftRoles> allShiftRoles = response.data.shiftRoles;
// Fetch current applications to filter out already booked shifts // Fetch current applications to filter out already booked shifts
final myAppsResponse = await _service.connector final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> myAppsResponse = await _service.connector
.getApplicationsByStaffId(staffId: staffId) .getApplicationsByStaffId(staffId: staffId)
.execute(); .execute();
final Set<String> appliedShiftIds = final Set<String> appliedShiftIds =
myAppsResponse.data.applications.map((a) => a.shiftId).toSet(); myAppsResponse.data.applications.map((dc.GetApplicationsByStaffIdApplications a) => a.shiftId).toSet();
final List<Shift> mappedShifts = []; final List<Shift> mappedShifts = <Shift>[];
for (final sr in allShiftRoles) { for (final dc.ListShiftRolesByVendorIdShiftRoles sr in allShiftRoles) {
if (appliedShiftIds.contains(sr.shiftId)) continue; if (appliedShiftIds.contains(sr.shiftId)) continue;
final DateTime? shiftDate = _service.toDateTime(sr.shift.date); final DateTime? shiftDate = _service.toDateTime(sr.shift.date);
final startDt = _service.toDateTime(sr.startTime); final DateTime? startDt = _service.toDateTime(sr.startTime);
final endDt = _service.toDateTime(sr.endTime); final DateTime? endDt = _service.toDateTime(sr.endTime);
final createdDt = _service.toDateTime(sr.createdAt); final DateTime? createdDt = _service.toDateTime(sr.createdAt);
mappedShifts.add( mappedShifts.add(
Shift( Shift(
@@ -96,8 +97,8 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
} }
if (query != null && query.isNotEmpty) { if (query != null && query.isNotEmpty) {
final lowerQuery = query.toLowerCase(); final String lowerQuery = query.toLowerCase();
return mappedShifts.where((s) { return mappedShifts.where((Shift s) {
return s.title.toLowerCase().contains(lowerQuery) || return s.title.toLowerCase().contains(lowerQuery) ||
s.clientName.toLowerCase().contains(lowerQuery); s.clientName.toLowerCase().contains(lowerQuery);
}).toList(); }).toList();
@@ -112,7 +113,7 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
return _service.run(() async { return _service.run(() async {
// Current schema doesn't have a specific "pending assignment" query that differs from confirmed // Current schema doesn't have a specific "pending assignment" query that differs from confirmed
// unless we filter by status. In the old repo it was returning an empty list. // unless we filter by status. In the old repo it was returning an empty list.
return []; return <Shift>[];
}); });
} }
@@ -124,10 +125,10 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
}) async { }) async {
return _service.run(() async { return _service.run(() async {
if (roleId != null && roleId.isNotEmpty) { if (roleId != null && roleId.isNotEmpty) {
final roleResult = await _service.connector final QueryResult<dc.GetShiftRoleByIdData, dc.GetShiftRoleByIdVariables> roleResult = await _service.connector
.getShiftRoleById(shiftId: shiftId, roleId: roleId) .getShiftRoleById(shiftId: shiftId, roleId: roleId)
.execute(); .execute();
final sr = roleResult.data.shiftRole; final dc.GetShiftRoleByIdShiftRole? sr = roleResult.data.shiftRole;
if (sr == null) return null; if (sr == null) return null;
final DateTime? startDt = _service.toDateTime(sr.startTime); final DateTime? startDt = _service.toDateTime(sr.startTime);
@@ -137,17 +138,17 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
bool hasApplied = false; bool hasApplied = false;
String status = 'open'; String status = 'open';
final appsResponse = await _service.connector final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> appsResponse = await _service.connector
.getApplicationsByStaffId(staffId: staffId) .getApplicationsByStaffId(staffId: staffId)
.execute(); .execute();
final app = appsResponse.data.applications final dc.GetApplicationsByStaffIdApplications? app = appsResponse.data.applications
.where((a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId) .where((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId && a.shiftRole.roleId == roleId)
.firstOrNull; .firstOrNull;
if (app != null) { if (app != null) {
hasApplied = true; hasApplied = true;
final s = app.status.stringValue; final String s = app.status.stringValue;
status = _mapApplicationStatus(s); status = _mapApplicationStatus(s);
} }
@@ -180,8 +181,8 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
); );
} }
final result = await _service.connector.getShiftById(id: shiftId).execute(); final QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> result = await _service.connector.getShiftById(id: shiftId).execute();
final s = result.data.shift; final dc.GetShiftByIdShift? s = result.data.shift;
if (s == null) return null; if (s == null) return null;
int? required; int? required;
@@ -189,17 +190,17 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
Break? breakInfo; Break? breakInfo;
try { try {
final rolesRes = await _service.connector final QueryResult<dc.ListShiftRolesByShiftIdData, dc.ListShiftRolesByShiftIdVariables> rolesRes = await _service.connector
.listShiftRolesByShiftId(shiftId: shiftId) .listShiftRolesByShiftId(shiftId: shiftId)
.execute(); .execute();
if (rolesRes.data.shiftRoles.isNotEmpty) { if (rolesRes.data.shiftRoles.isNotEmpty) {
required = 0; required = 0;
filled = 0; filled = 0;
for (var r in rolesRes.data.shiftRoles) { for (dc.ListShiftRolesByShiftIdShiftRoles r in rolesRes.data.shiftRoles) {
required = (required ?? 0) + r.count; required = (required ?? 0) + r.count;
filled = (filled ?? 0) + (r.assigned ?? 0); filled = (filled ?? 0) + (r.assigned ?? 0);
} }
final firstRole = rolesRes.data.shiftRoles.first; final dc.ListShiftRolesByShiftIdShiftRoles firstRole = rolesRes.data.shiftRoles.first;
breakInfo = BreakAdapter.fromData( breakInfo = BreakAdapter.fromData(
isPaid: firstRole.isBreakPaid ?? false, isPaid: firstRole.isBreakPaid ?? false,
breakTime: firstRole.breakType?.stringValue, breakTime: firstRole.breakType?.stringValue,
@@ -207,9 +208,9 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
} }
} catch (_) {} } catch (_) {}
final startDt = _service.toDateTime(s.startTime); final DateTime? startDt = _service.toDateTime(s.startTime);
final endDt = _service.toDateTime(s.endTime); final DateTime? endDt = _service.toDateTime(s.endTime);
final createdDt = _service.toDateTime(s.createdAt); final DateTime? createdDt = _service.toDateTime(s.createdAt);
return Shift( return Shift(
id: s.id, id: s.id,
@@ -243,17 +244,17 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
String? roleId, String? roleId,
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final targetRoleId = roleId ?? ''; final String targetRoleId = roleId ?? '';
if (targetRoleId.isEmpty) throw Exception('Missing role id.'); if (targetRoleId.isEmpty) throw Exception('Missing role id.');
final roleResult = await _service.connector final QueryResult<dc.GetShiftRoleByIdData, dc.GetShiftRoleByIdVariables> roleResult = await _service.connector
.getShiftRoleById(shiftId: shiftId, roleId: targetRoleId) .getShiftRoleById(shiftId: shiftId, roleId: targetRoleId)
.execute(); .execute();
final role = roleResult.data.shiftRole; final dc.GetShiftRoleByIdShiftRole? role = roleResult.data.shiftRole;
if (role == null) throw Exception('Shift role not found'); if (role == null) throw Exception('Shift role not found');
final shiftResult = await _service.connector.getShiftById(id: shiftId).execute(); final QueryResult<dc.GetShiftByIdData, dc.GetShiftByIdVariables> shiftResult = await _service.connector.getShiftById(id: shiftId).execute();
final shift = shiftResult.data.shift; final dc.GetShiftByIdShift? shift = shiftResult.data.shift;
if (shift == null) throw Exception('Shift not found'); if (shift == null) throw Exception('Shift not found');
// Validate daily limit // Validate daily limit
@@ -262,7 +263,7 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
final DateTime dayStartUtc = DateTime.utc(shiftDate.year, shiftDate.month, shiftDate.day); final DateTime dayStartUtc = DateTime.utc(shiftDate.year, shiftDate.month, shiftDate.day);
final DateTime dayEndUtc = dayStartUtc.add(const Duration(days: 1)).subtract(const Duration(microseconds: 1)); final DateTime dayEndUtc = dayStartUtc.add(const Duration(days: 1)).subtract(const Duration(microseconds: 1));
final validationResponse = await _service.connector final QueryResult<dc.VaidateDayStaffApplicationData, dc.VaidateDayStaffApplicationVariables> validationResponse = await _service.connector
.vaidateDayStaffApplication(staffId: staffId) .vaidateDayStaffApplication(staffId: staffId)
.dayStart(_service.toTimestamp(dayStartUtc)) .dayStart(_service.toTimestamp(dayStartUtc))
.dayEnd(_service.toTimestamp(dayEndUtc)) .dayEnd(_service.toTimestamp(dayEndUtc))
@@ -274,7 +275,7 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
} }
// Check for existing application // Check for existing application
final existingAppRes = await _service.connector final QueryResult<dc.GetApplicationByStaffShiftAndRoleData, dc.GetApplicationByStaffShiftAndRoleVariables> existingAppRes = await _service.connector
.getApplicationByStaffShiftAndRole( .getApplicationByStaffShiftAndRole(
staffId: staffId, staffId: staffId,
shiftId: shiftId, shiftId: shiftId,
@@ -294,7 +295,7 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
String? createdAppId; String? createdAppId;
try { try {
final createRes = await _service.connector.createApplication( final OperationResult<dc.CreateApplicationData, dc.CreateApplicationVariables> createRes = await _service.connector.createApplication(
shiftId: shiftId, shiftId: shiftId,
staffId: staffId, staffId: staffId,
roleId: targetRoleId, roleId: targetRoleId,
@@ -343,19 +344,19 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
Future<List<Shift>> getCancelledShifts({required String staffId}) async { Future<List<Shift>> getCancelledShifts({required String staffId}) async {
return _service.run(() async { return _service.run(() async {
// Logic would go here to fetch by REJECTED status if needed // Logic would go here to fetch by REJECTED status if needed
return []; return <Shift>[];
}); });
} }
@override @override
Future<List<Shift>> getHistoryShifts({required String staffId}) async { Future<List<Shift>> getHistoryShifts({required String staffId}) async {
return _service.run(() async { return _service.run(() async {
final response = await _service.connector final QueryResult<dc.ListCompletedApplicationsByStaffIdData, dc.ListCompletedApplicationsByStaffIdVariables> response = await _service.connector
.listCompletedApplicationsByStaffId(staffId: staffId) .listCompletedApplicationsByStaffId(staffId: staffId)
.execute(); .execute();
final List<Shift> shifts = []; final List<Shift> shifts = <Shift>[];
for (final app in response.data.applications) { for (final dc.ListCompletedApplicationsByStaffIdApplications app in response.data.applications) {
final String roleName = app.shiftRole.role.name; final String roleName = app.shiftRole.role.name;
final String orderName = (app.shift.order.eventName ?? '').trim().isNotEmpty final String orderName = (app.shift.order.eventName ?? '').trim().isNotEmpty
? app.shift.order.eventName! ? app.shift.order.eventName!
@@ -478,12 +479,12 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
) async { ) async {
return _service.run(() async { return _service.run(() async {
// First try to find the application // First try to find the application
final appsResponse = await _service.connector final QueryResult<dc.GetApplicationsByStaffIdData, dc.GetApplicationsByStaffIdVariables> appsResponse = await _service.connector
.getApplicationsByStaffId(staffId: staffId) .getApplicationsByStaffId(staffId: staffId)
.execute(); .execute();
final app = appsResponse.data.applications final dc.GetApplicationsByStaffIdApplications? app = appsResponse.data.applications
.where((a) => a.shiftId == shiftId) .where((dc.GetApplicationsByStaffIdApplications a) => a.shiftId == shiftId)
.firstOrNull; .firstOrNull;
if (app != null) { if (app != null) {
@@ -493,12 +494,12 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
.execute(); .execute();
} else if (newStatus == dc.ApplicationStatus.REJECTED) { } else if (newStatus == dc.ApplicationStatus.REJECTED) {
// If declining but no app found, create a rejected application // If declining but no app found, create a rejected application
final rolesRes = await _service.connector final QueryResult<dc.ListShiftRolesByShiftIdData, dc.ListShiftRolesByShiftIdVariables> rolesRes = await _service.connector
.listShiftRolesByShiftId(shiftId: shiftId) .listShiftRolesByShiftId(shiftId: shiftId)
.execute(); .execute();
if (rolesRes.data.shiftRoles.isNotEmpty) { if (rolesRes.data.shiftRoles.isNotEmpty) {
final firstRole = rolesRes.data.shiftRoles.first; final dc.ListShiftRolesByShiftIdShiftRoles firstRole = rolesRes.data.shiftRoles.first;
await _service.connector.createApplication( await _service.connector.createApplication(
shiftId: shiftId, shiftId: shiftId,
staffId: staffId, staffId: staffId,
@@ -513,3 +514,4 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository {
}); });
} }
} }

View File

@@ -105,10 +105,10 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
/// Checks if personal info is complete. /// Checks if personal info is complete.
bool _isPersonalInfoComplete(GetStaffPersonalInfoCompletionStaff? staff) { bool _isPersonalInfoComplete(GetStaffPersonalInfoCompletionStaff? staff) {
if (staff == null) return false; if (staff == null) return false;
final String? fullName = staff.fullName; final String fullName = staff.fullName;
final String? email = staff.email; final String? email = staff.email;
final String? phone = staff.phone; final String? phone = staff.phone;
return (fullName?.trim().isNotEmpty ?? false) && return (fullName.trim().isNotEmpty ?? false) &&
(email?.trim().isNotEmpty ?? false) && (email?.trim().isNotEmpty ?? false) &&
(phone?.trim().isNotEmpty ?? false); (phone?.trim().isNotEmpty ?? false);
} }

View File

@@ -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:firebase_auth/firebase_auth.dart' as firebase; import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@@ -197,7 +198,6 @@ class DataConnectService with DataErrorHandler, SessionHandlerMixin {
} }
/// Executes an operation with centralized error handling. /// Executes an operation with centralized error handling.
@override
Future<T> run<T>( Future<T> run<T>(
Future<T> Function() operation, { Future<T> Function() operation, {
bool requiresAuthentication = true, bool requiresAuthentication = true,

View File

@@ -1,10 +1,4 @@
class ClientBusinessSession { class ClientBusinessSession {
final String id;
final String businessName;
final String? email;
final String? city;
final String? contactName;
final String? companyLogoUrl;
const ClientBusinessSession({ const ClientBusinessSession({
required this.id, required this.id,
@@ -14,15 +8,23 @@ class ClientBusinessSession {
this.contactName, this.contactName,
this.companyLogoUrl, this.companyLogoUrl,
}); });
final String id;
final String businessName;
final String? email;
final String? city;
final String? contactName;
final String? companyLogoUrl;
} }
class ClientSession { class ClientSession {
final ClientBusinessSession? business;
const ClientSession({required this.business}); const ClientSession({required this.business});
final ClientBusinessSession? business;
} }
class ClientSessionStore { class ClientSessionStore {
ClientSessionStore._();
ClientSession? _session; ClientSession? _session;
ClientSession? get session => _session; ClientSession? get session => _session;
@@ -36,6 +38,4 @@ class ClientSessionStore {
} }
static final ClientSessionStore instance = ClientSessionStore._(); static final ClientSessionStore instance = ClientSessionStore._();
ClientSessionStore._();
} }

View File

@@ -85,8 +85,9 @@ class UiTheme {
overlayColor: WidgetStateProperty.resolveWith(( overlayColor: WidgetStateProperty.resolveWith((
Set<WidgetState> states, Set<WidgetState> states,
) { ) {
if (states.contains(WidgetState.hovered)) if (states.contains(WidgetState.hovered)) {
return UiColors.buttonPrimaryHover; return UiColors.buttonPrimaryHover;
}
return null; return null;
}), }),
), ),

View File

@@ -6,6 +6,19 @@ import '../ui_icons.dart';
/// ///
/// This widget provides a consistent look and feel for top app bars across the application. /// This widget provides a consistent look and feel for top app bars across the application.
class UiAppBar extends StatelessWidget implements PreferredSizeWidget { class UiAppBar extends StatelessWidget implements PreferredSizeWidget {
const UiAppBar({
super.key,
this.title,
this.titleWidget,
this.leading,
this.actions,
this.height = kToolbarHeight,
this.centerTitle = true,
this.onLeadingPressed,
this.showBackButton = true,
this.bottom,
});
/// The title text to display in the app bar. /// The title text to display in the app bar.
final String? title; final String? title;
@@ -36,19 +49,6 @@ class UiAppBar extends StatelessWidget implements PreferredSizeWidget {
/// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can be used at the bottom of an app bar. /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can be used at the bottom of an app bar.
final PreferredSizeWidget? bottom; final PreferredSizeWidget? bottom;
const UiAppBar({
super.key,
this.title,
this.titleWidget,
this.leading,
this.actions,
this.height = kToolbarHeight,
this.centerTitle = true,
this.onLeadingPressed,
this.showBackButton = true,
this.bottom,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(

View File

@@ -3,6 +3,96 @@ import '../ui_constants.dart';
/// A custom button widget with different variants and icon support. /// A custom button widget with different variants and icon support.
class UiButton extends StatelessWidget { class UiButton extends StatelessWidget {
/// Creates a [UiButton] with a custom button builder.
const UiButton({
super.key,
this.text,
this.child,
required this.buttonBuilder,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a primary button using [ElevatedButton].
const UiButton.primary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _elevatedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a secondary button using [OutlinedButton].
const UiButton.secondary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _outlinedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a text button using [TextButton].
const UiButton.text({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a ghost button (transparent background).
const UiButton.ghost({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// The text to display on the button. /// The text to display on the button.
final String? text; final String? text;
@@ -39,100 +129,10 @@ class UiButton extends StatelessWidget {
) )
buttonBuilder; buttonBuilder;
/// Creates a [UiButton] with a custom button builder.
const UiButton({
super.key,
this.text,
this.child,
required this.buttonBuilder,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a primary button using [ElevatedButton].
UiButton.primary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _elevatedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a secondary button using [OutlinedButton].
UiButton.secondary({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _outlinedButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a text button using [TextButton].
UiButton.text({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
/// Creates a ghost button (transparent background).
UiButton.ghost({
super.key,
this.text,
this.child,
this.onPressed,
this.leadingIcon,
this.trailingIcon,
this.style,
this.iconSize = 20,
this.size = UiButtonSize.large,
this.fullWidth = false,
}) : buttonBuilder = _textButtonBuilder,
assert(
text != null || child != null,
'Either text or child must be provided',
);
@override @override
/// Builds the button UI. /// Builds the button UI.
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ButtonStyle? mergedStyle = style != null final ButtonStyle mergedStyle = style != null
? _getSizeStyle().merge(style) ? _getSizeStyle().merge(style)
: _getSizeStyle(); : _getSizeStyle();

View File

@@ -29,6 +29,19 @@ enum UiChipVariant {
/// A custom chip widget with supports for different sizes, themes, and icons. /// A custom chip widget with supports for different sizes, themes, and icons.
class UiChip extends StatelessWidget { class UiChip extends StatelessWidget {
/// Creates a [UiChip].
const UiChip({
super.key,
required this.label,
this.size = UiChipSize.medium,
this.variant = UiChipVariant.secondary,
this.leadingIcon,
this.trailingIcon,
this.onTap,
this.onTrailingIconTap,
this.isSelected = false,
});
/// The text label to display. /// The text label to display.
final String label; final String label;
@@ -53,19 +66,6 @@ class UiChip extends StatelessWidget {
/// Whether the chip is currently selected/active. /// Whether the chip is currently selected/active.
final bool isSelected; final bool isSelected;
/// Creates a [UiChip].
const UiChip({
super.key,
required this.label,
this.size = UiChipSize.medium,
this.variant = UiChipVariant.secondary,
this.leadingIcon,
this.trailingIcon,
this.onTap,
this.onTrailingIconTap,
this.isSelected = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color backgroundColor = _getBackgroundColor(); final Color backgroundColor = _getBackgroundColor();

View File

@@ -5,26 +5,6 @@ import '../ui_constants.dart';
/// A custom icon button with blur effect and different variants. /// A custom icon button with blur effect and different variants.
class UiIconButton extends StatelessWidget { class UiIconButton extends StatelessWidget {
/// The icon to display.
final IconData icon;
/// The size of the icon button.
final double size;
/// The size of the icon.
final double iconSize;
/// The background color of the button.
final Color backgroundColor;
/// The color of the icon.
final Color iconColor;
/// Whether to apply blur effect.
final bool useBlur;
/// Callback when the button is tapped.
final VoidCallback? onTap;
/// Creates a [UiIconButton] with custom properties. /// Creates a [UiIconButton] with custom properties.
const UiIconButton({ const UiIconButton({
@@ -59,6 +39,26 @@ class UiIconButton extends StatelessWidget {
}) : backgroundColor = UiColors.primary.withAlpha(96), }) : backgroundColor = UiColors.primary.withAlpha(96),
iconColor = UiColors.primary, iconColor = UiColors.primary,
useBlur = true; useBlur = true;
/// The icon to display.
final IconData icon;
/// The size of the icon button.
final double size;
/// The size of the icon.
final double iconSize;
/// The background color of the button.
final Color backgroundColor;
/// The color of the icon.
final Color iconColor;
/// Whether to apply blur effect.
final bool useBlur;
/// Callback when the button is tapped.
final VoidCallback? onTap;
@override @override
/// Builds the icon button UI. /// Builds the icon button UI.

View File

@@ -8,6 +8,26 @@ import '../ui_colors.dart';
/// ///
/// This widget combines a label and a [TextField] with consistent styling. /// This widget combines a label and a [TextField] with consistent styling.
class UiTextField extends StatelessWidget { class UiTextField extends StatelessWidget {
const UiTextField({
super.key,
this.label,
this.hintText,
this.onChanged,
this.controller,
this.keyboardType,
this.maxLines = 1,
this.obscureText = false,
this.textInputAction,
this.onSubmitted,
this.autofocus = false,
this.inputFormatters,
this.prefixIcon,
this.suffixIcon,
this.suffix,
this.readOnly = false,
this.onTap,
});
/// The label text to display above the text field. /// The label text to display above the text field.
final String? label; final String? label;
@@ -56,26 +76,6 @@ class UiTextField extends StatelessWidget {
/// Callback when the text field is tapped. /// Callback when the text field is tapped.
final VoidCallback? onTap; final VoidCallback? onTap;
const UiTextField({
super.key,
this.label,
this.hintText,
this.onChanged,
this.controller,
this.keyboardType,
this.maxLines = 1,
this.obscureText = false,
this.textInputAction,
this.onSubmitted,
this.autofocus = false,
this.inputFormatters,
this.prefixIcon,
this.suffixIcon,
this.suffix,
this.readOnly = false,
this.onTap,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(

View File

@@ -2,18 +2,18 @@ import '../../entities/availability/availability_slot.dart';
/// Adapter for [AvailabilitySlot] domain entity. /// Adapter for [AvailabilitySlot] domain entity.
class AvailabilityAdapter { class AvailabilityAdapter {
static const Map<String, Map<String, String>> _slotDefinitions = { static const Map<String, Map<String, String>> _slotDefinitions = <String, Map<String, String>>{
'MORNING': { 'MORNING': <String, String>{
'id': 'morning', 'id': 'morning',
'label': 'Morning', 'label': 'Morning',
'timeRange': '4:00 AM - 12:00 PM', 'timeRange': '4:00 AM - 12:00 PM',
}, },
'AFTERNOON': { 'AFTERNOON': <String, String>{
'id': 'afternoon', 'id': 'afternoon',
'label': 'Afternoon', 'label': 'Afternoon',
'timeRange': '12:00 PM - 6:00 PM', 'timeRange': '12:00 PM - 6:00 PM',
}, },
'EVENING': { 'EVENING': <String, String>{
'id': 'evening', 'id': 'evening',
'label': 'Evening', 'label': 'Evening',
'timeRange': '6:00 PM - 12:00 AM', 'timeRange': '6:00 PM - 12:00 AM',
@@ -22,7 +22,7 @@ class AvailabilityAdapter {
/// Converts a backend slot name (e.g. 'MORNING') to a Domain [AvailabilitySlot]. /// Converts a backend slot name (e.g. 'MORNING') to a Domain [AvailabilitySlot].
static AvailabilitySlot fromPrimitive(String slotName, {bool isAvailable = false}) { static AvailabilitySlot fromPrimitive(String slotName, {bool isAvailable = false}) {
final def = _slotDefinitions[slotName.toUpperCase()] ?? _slotDefinitions['MORNING']!; final Map<String, String> def = _slotDefinitions[slotName.toUpperCase()] ?? _slotDefinitions['MORNING']!;
return AvailabilitySlot( return AvailabilitySlot(
id: def['id']!, id: def['id']!,
label: def['label']!, label: def['label']!,

View File

@@ -1,4 +1,3 @@
import '../../entities/shifts/shift.dart';
import '../../entities/clock_in/attendance_status.dart'; import '../../entities/clock_in/attendance_status.dart';
/// Adapter for Clock In related data. /// Adapter for Clock In related data.

View File

@@ -18,7 +18,7 @@ class TaxFormAdapter {
final TaxFormType formType = _stringToType(type); final TaxFormType formType = _stringToType(type);
final TaxFormStatus formStatus = _stringToStatus(status); final TaxFormStatus formStatus = _stringToStatus(status);
final Map<String, dynamic> formDetails = final Map<String, dynamic> formDetails =
formData is Map ? Map<String, dynamic>.from(formData as Map) : <String, dynamic>{}; formData is Map ? Map<String, dynamic>.from(formData) : <String, dynamic>{};
if (formType == TaxFormType.i9) { if (formType == TaxFormType.i9) {
return I9TaxForm( return I9TaxForm(

View File

@@ -2,10 +2,6 @@ import 'package:equatable/equatable.dart';
/// Represents a specific time slot within a day (e.g., Morning, Afternoon, Evening). /// Represents a specific time slot within a day (e.g., Morning, Afternoon, Evening).
class AvailabilitySlot extends Equatable { class AvailabilitySlot extends Equatable {
final String id;
final String label;
final String timeRange;
final bool isAvailable;
const AvailabilitySlot({ const AvailabilitySlot({
required this.id, required this.id,
@@ -13,6 +9,10 @@ class AvailabilitySlot extends Equatable {
required this.timeRange, required this.timeRange,
this.isAvailable = true, this.isAvailable = true,
}); });
final String id;
final String label;
final String timeRange;
final bool isAvailable;
AvailabilitySlot copyWith({ AvailabilitySlot copyWith({
String? id, String? id,
@@ -29,5 +29,5 @@ class AvailabilitySlot extends Equatable {
} }
@override @override
List<Object?> get props => [id, label, timeRange, isAvailable]; List<Object?> get props => <Object?>[id, label, timeRange, isAvailable];
} }

View File

@@ -4,15 +4,15 @@ import 'availability_slot.dart';
/// Represents availability configuration for a specific date. /// Represents availability configuration for a specific date.
class DayAvailability extends Equatable { class DayAvailability extends Equatable {
final DateTime date;
final bool isAvailable;
final List<AvailabilitySlot> slots;
const DayAvailability({ const DayAvailability({
required this.date, required this.date,
this.isAvailable = false, this.isAvailable = false,
this.slots = const [], this.slots = const <AvailabilitySlot>[],
}); });
final DateTime date;
final bool isAvailable;
final List<AvailabilitySlot> slots;
DayAvailability copyWith({ DayAvailability copyWith({
DateTime? date, DateTime? date,
@@ -27,5 +27,5 @@ class DayAvailability extends Equatable {
} }
@override @override
List<Object?> get props => [date, isAvailable, slots]; List<Object?> get props => <Object?>[date, isAvailable, slots];
} }

View File

@@ -2,11 +2,6 @@ import 'package:equatable/equatable.dart';
/// Simple entity to hold attendance state /// Simple entity to hold attendance state
class AttendanceStatus extends Equatable { class AttendanceStatus extends Equatable {
final bool isCheckedIn;
final DateTime? checkInTime;
final DateTime? checkOutTime;
final String? activeShiftId;
final String? activeApplicationId;
const AttendanceStatus({ const AttendanceStatus({
this.isCheckedIn = false, this.isCheckedIn = false,
@@ -15,9 +10,14 @@ class AttendanceStatus extends Equatable {
this.activeShiftId, this.activeShiftId,
this.activeApplicationId, this.activeApplicationId,
}); });
final bool isCheckedIn;
final DateTime? checkInTime;
final DateTime? checkOutTime;
final String? activeShiftId;
final String? activeApplicationId;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
isCheckedIn, isCheckedIn,
checkInTime, checkInTime,
checkOutTime, checkOutTime,

View File

@@ -2,10 +2,6 @@ import 'package:equatable/equatable.dart';
/// Summary of staff earnings. /// Summary of staff earnings.
class PaymentSummary extends Equatable { class PaymentSummary extends Equatable {
final double weeklyEarnings;
final double monthlyEarnings;
final double pendingEarnings;
final double totalEarnings;
const PaymentSummary({ const PaymentSummary({
required this.weeklyEarnings, required this.weeklyEarnings,
@@ -13,9 +9,13 @@ class PaymentSummary extends Equatable {
required this.pendingEarnings, required this.pendingEarnings,
required this.totalEarnings, required this.totalEarnings,
}); });
final double weeklyEarnings;
final double monthlyEarnings;
final double pendingEarnings;
final double totalEarnings;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
weeklyEarnings, weeklyEarnings,
monthlyEarnings, monthlyEarnings,
pendingEarnings, pendingEarnings,

View File

@@ -23,6 +23,21 @@ enum TimeCardStatus {
/// Represents a time card for a staff member. /// Represents a time card for a staff member.
class TimeCard extends Equatable { class TimeCard extends Equatable {
/// Creates a [TimeCard].
const TimeCard({
required this.id,
required this.shiftTitle,
required this.clientName,
required this.date,
required this.startTime,
required this.endTime,
required this.totalHours,
required this.hourlyRate,
required this.totalPay,
required this.status,
this.location,
});
/// Unique identifier of the time card (often matches Application ID). /// Unique identifier of the time card (often matches Application ID).
final String id; final String id;
/// Title of the shift. /// Title of the shift.
@@ -46,23 +61,8 @@ class TimeCard extends Equatable {
/// Location name. /// Location name.
final String? location; final String? location;
/// Creates a [TimeCard].
const TimeCard({
required this.id,
required this.shiftTitle,
required this.clientName,
required this.date,
required this.startTime,
required this.endTime,
required this.totalHours,
required this.hourlyRate,
required this.totalPay,
required this.status,
this.location,
});
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
id, id,
shiftTitle, shiftTitle,
clientName, clientName,

View File

@@ -26,7 +26,7 @@ class PermanentOrder extends Equatable {
final Map<String, double> roleRates; final Map<String, double> roleRates;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
startDate, startDate,
permanentDays, permanentDays,
positions, positions,

View File

@@ -4,6 +4,15 @@ import 'package:equatable/equatable.dart';
/// ///
/// Attire items are specific clothing or equipment required for jobs. /// Attire items are specific clothing or equipment required for jobs.
class AttireItem extends Equatable { class AttireItem extends Equatable {
/// Creates an [AttireItem].
const AttireItem({
required this.id,
required this.label,
this.iconName,
this.imageUrl,
this.isMandatory = false,
});
/// Unique identifier of the attire item. /// Unique identifier of the attire item.
final String id; final String id;
@@ -19,15 +28,6 @@ class AttireItem extends Equatable {
/// Whether this item is mandatory for onboarding. /// Whether this item is mandatory for onboarding.
final bool isMandatory; final bool isMandatory;
/// Creates an [AttireItem].
const AttireItem({
required this.id,
required this.label,
this.iconName,
this.imageUrl,
this.isMandatory = false,
});
@override @override
List<Object?> get props => <Object?>[id, label, iconName, imageUrl, isMandatory]; List<Object?> get props => <Object?>[id, label, iconName, imageUrl, isMandatory];
} }

View File

@@ -22,7 +22,7 @@ enum ExperienceSkill {
static ExperienceSkill? fromString(String value) { static ExperienceSkill? fromString(String value) {
try { try {
return ExperienceSkill.values.firstWhere((e) => e.value == value); return ExperienceSkill.values.firstWhere((ExperienceSkill e) => e.value == value);
} catch (_) { } catch (_) {
return null; return null;
} }

View File

@@ -13,7 +13,7 @@ enum Industry {
static Industry? fromString(String value) { static Industry? fromString(String value) {
try { try {
return Industry.values.firstWhere((e) => e.value == value); return Industry.values.firstWhere((Industry e) => e.value == value);
} catch (_) { } catch (_) {
return null; return null;
} }

View File

@@ -11,6 +11,17 @@ enum DocumentStatus {
/// Represents a staff compliance document. /// Represents a staff compliance document.
class StaffDocument extends Equatable { class StaffDocument extends Equatable {
const StaffDocument({
required this.id,
required this.staffId,
required this.documentId,
required this.name,
this.description,
required this.status,
this.documentUrl,
this.expiryDate,
});
/// The unique identifier of the staff document record. /// The unique identifier of the staff document record.
final String id; final String id;
@@ -35,19 +46,8 @@ class StaffDocument extends Equatable {
/// The expiry date of the document. /// The expiry date of the document.
final DateTime? expiryDate; final DateTime? expiryDate;
const StaffDocument({
required this.id,
required this.staffId,
required this.documentId,
required this.name,
this.description,
required this.status,
this.documentUrl,
this.expiryDate,
});
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
id, id,
staffId, staffId,
documentId, documentId,

View File

@@ -5,6 +5,18 @@ enum TaxFormType { i9, w4 }
enum TaxFormStatus { notStarted, inProgress, submitted, approved, rejected } enum TaxFormStatus { notStarted, inProgress, submitted, approved, rejected }
abstract class TaxForm extends Equatable { abstract class TaxForm extends Equatable {
const TaxForm({
required this.id,
required this.title,
this.subtitle,
this.description,
this.status = TaxFormStatus.notStarted,
this.staffId,
this.formData = const <String, dynamic>{},
this.createdAt,
this.updatedAt,
});
final String id; final String id;
TaxFormType get type; TaxFormType get type;
final String title; final String title;
@@ -16,20 +28,8 @@ abstract class TaxForm extends Equatable {
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
const TaxForm({
required this.id,
required this.title,
this.subtitle,
this.description,
this.status = TaxFormStatus.notStarted,
this.staffId,
this.formData = const {},
this.createdAt,
this.updatedAt,
});
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
id, id,
type, type,
title, title,

View File

@@ -1,10 +1,7 @@
// 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:equatable/equatable.dart';
class CoverageReport extends Equatable { class CoverageReport extends Equatable {
final double overallCoverage;
final int totalNeeded;
final int totalFilled;
final List<CoverageDay> dailyCoverage;
const CoverageReport({ const CoverageReport({
required this.overallCoverage, required this.overallCoverage,
@@ -12,16 +9,16 @@ class CoverageReport extends Equatable {
required this.totalFilled, required this.totalFilled,
required this.dailyCoverage, required this.dailyCoverage,
}); });
final double overallCoverage;
final int totalNeeded;
final int totalFilled;
final List<CoverageDay> dailyCoverage;
@override @override
List<Object?> get props => [overallCoverage, totalNeeded, totalFilled, dailyCoverage]; List<Object?> get props => <Object?>[overallCoverage, totalNeeded, totalFilled, dailyCoverage];
} }
class CoverageDay extends Equatable { class CoverageDay extends Equatable {
final DateTime date;
final int needed;
final int filled;
final double percentage;
const CoverageDay({ const CoverageDay({
required this.date, required this.date,
@@ -29,7 +26,12 @@ class CoverageDay extends Equatable {
required this.filled, required this.filled,
required this.percentage, required this.percentage,
}); });
final DateTime date;
final int needed;
final int filled;
final double percentage;
@override @override
List<Object?> get props => [date, needed, filled, percentage]; List<Object?> get props => <Object?>[date, needed, filled, percentage];
} }

View File

@@ -1,11 +1,7 @@
// 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:equatable/equatable.dart';
class DailyOpsReport extends Equatable { class DailyOpsReport extends Equatable {
final int scheduledShifts;
final int workersConfirmed;
final int inProgressShifts;
final int completedShifts;
final List<DailyOpsShift> shifts;
const DailyOpsReport({ const DailyOpsReport({
required this.scheduledShifts, required this.scheduledShifts,
@@ -14,9 +10,14 @@ class DailyOpsReport extends Equatable {
required this.completedShifts, required this.completedShifts,
required this.shifts, required this.shifts,
}); });
final int scheduledShifts;
final int workersConfirmed;
final int inProgressShifts;
final int completedShifts;
final List<DailyOpsShift> shifts;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
scheduledShifts, scheduledShifts,
workersConfirmed, workersConfirmed,
inProgressShifts, inProgressShifts,
@@ -26,15 +27,6 @@ class DailyOpsReport extends Equatable {
} }
class DailyOpsShift extends Equatable { class DailyOpsShift extends Equatable {
final String id;
final String title;
final String location;
final DateTime startTime;
final DateTime endTime;
final int workersNeeded;
final int filled;
final String status;
final double? hourlyRate;
const DailyOpsShift({ const DailyOpsShift({
required this.id, required this.id,
@@ -47,9 +39,18 @@ class DailyOpsShift extends Equatable {
required this.status, required this.status,
this.hourlyRate, this.hourlyRate,
}); });
final String id;
final String title;
final String location;
final DateTime startTime;
final DateTime endTime;
final int workersNeeded;
final int filled;
final String status;
final double? hourlyRate;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
id, id,
title, title,
location, location,
@@ -61,3 +62,4 @@ class DailyOpsShift extends Equatable {
hourlyRate, hourlyRate,
]; ];
} }

View File

@@ -1,6 +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:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class ForecastReport extends Equatable { class ForecastReport extends Equatable {
const ForecastReport({
required this.projectedSpend,
required this.projectedWorkers,
required this.averageLaborCost,
required this.chartData,
this.totalShifts = 0,
this.totalHours = 0.0,
this.avgWeeklySpend = 0.0,
this.weeklyBreakdown = const <ForecastWeek>[],
});
final double projectedSpend; final double projectedSpend;
final int projectedWorkers; final int projectedWorkers;
final double averageLaborCost; final double averageLaborCost;
@@ -12,19 +24,8 @@ class ForecastReport extends Equatable {
final double avgWeeklySpend; final double avgWeeklySpend;
final List<ForecastWeek> weeklyBreakdown; final List<ForecastWeek> weeklyBreakdown;
const ForecastReport({
required this.projectedSpend,
required this.projectedWorkers,
required this.averageLaborCost,
required this.chartData,
this.totalShifts = 0,
this.totalHours = 0.0,
this.avgWeeklySpend = 0.0,
this.weeklyBreakdown = const [],
});
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
projectedSpend, projectedSpend,
projectedWorkers, projectedWorkers,
averageLaborCost, averageLaborCost,
@@ -37,26 +38,21 @@ class ForecastReport extends Equatable {
} }
class ForecastPoint extends Equatable { class ForecastPoint extends Equatable {
final DateTime date;
final double projectedCost;
final int workersNeeded;
const ForecastPoint({ const ForecastPoint({
required this.date, required this.date,
required this.projectedCost, required this.projectedCost,
required this.workersNeeded, required this.workersNeeded,
}); });
final DateTime date;
final double projectedCost;
final int workersNeeded;
@override @override
List<Object?> get props => [date, projectedCost, workersNeeded]; List<Object?> get props => <Object?>[date, projectedCost, workersNeeded];
} }
class ForecastWeek extends Equatable { class ForecastWeek extends Equatable {
final int weekNumber;
final double totalCost;
final int shiftsCount;
final double hoursCount;
final double avgCostPerShift;
const ForecastWeek({ const ForecastWeek({
required this.weekNumber, required this.weekNumber,
@@ -65,9 +61,14 @@ class ForecastWeek extends Equatable {
required this.hoursCount, required this.hoursCount,
required this.avgCostPerShift, required this.avgCostPerShift,
}); });
final int weekNumber;
final double totalCost;
final int shiftsCount;
final double hoursCount;
final double avgCostPerShift;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
weekNumber, weekNumber,
totalCost, totalCost,
shiftsCount, shiftsCount,
@@ -75,3 +76,4 @@ class ForecastWeek extends Equatable {
avgCostPerShift, avgCostPerShift,
]; ];
} }

View File

@@ -1,25 +1,22 @@
// 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:equatable/equatable.dart';
class NoShowReport extends Equatable { class NoShowReport extends Equatable {
final int totalNoShows;
final double noShowRate;
final List<NoShowWorker> flaggedWorkers;
const NoShowReport({ const NoShowReport({
required this.totalNoShows, required this.totalNoShows,
required this.noShowRate, required this.noShowRate,
required this.flaggedWorkers, required this.flaggedWorkers,
}); });
final int totalNoShows;
final double noShowRate;
final List<NoShowWorker> flaggedWorkers;
@override @override
List<Object?> get props => [totalNoShows, noShowRate, flaggedWorkers]; List<Object?> get props => <Object?>[totalNoShows, noShowRate, flaggedWorkers];
} }
class NoShowWorker extends Equatable { class NoShowWorker extends Equatable {
final String id;
final String fullName;
final int noShowCount;
final double reliabilityScore;
const NoShowWorker({ const NoShowWorker({
required this.id, required this.id,
@@ -27,7 +24,12 @@ class NoShowWorker extends Equatable {
required this.noShowCount, required this.noShowCount,
required this.reliabilityScore, required this.reliabilityScore,
}); });
final String id;
final String fullName;
final int noShowCount;
final double reliabilityScore;
@override @override
List<Object?> get props => [id, fullName, noShowCount, reliabilityScore]; List<Object?> get props => <Object?>[id, fullName, noShowCount, reliabilityScore];
} }

View File

@@ -1,11 +1,7 @@
// 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:equatable/equatable.dart';
class PerformanceReport extends Equatable { class PerformanceReport extends Equatable {
final double fillRate;
final double completionRate;
final double onTimeRate;
final double avgFillTimeHours; // in hours
final List<PerformanceMetric> keyPerformanceIndicators;
const PerformanceReport({ const PerformanceReport({
required this.fillRate, required this.fillRate,
@@ -14,22 +10,28 @@ class PerformanceReport extends Equatable {
required this.avgFillTimeHours, required this.avgFillTimeHours,
required this.keyPerformanceIndicators, required this.keyPerformanceIndicators,
}); });
final double fillRate;
final double completionRate;
final double onTimeRate;
final double avgFillTimeHours; // in hours
final List<PerformanceMetric> keyPerformanceIndicators;
@override @override
List<Object?> get props => [fillRate, completionRate, onTimeRate, avgFillTimeHours, keyPerformanceIndicators]; List<Object?> get props => <Object?>[fillRate, completionRate, onTimeRate, avgFillTimeHours, keyPerformanceIndicators];
} }
class PerformanceMetric extends Equatable { class PerformanceMetric extends Equatable { // e.g. 0.05 for +5%
final String label;
final String value;
final double trend; // e.g. 0.05 for +5%
const PerformanceMetric({ const PerformanceMetric({
required this.label, required this.label,
required this.value, required this.value,
required this.trend, required this.trend,
}); });
final String label;
final String value;
final double trend;
@override @override
List<Object?> get props => [label, value, trend]; List<Object?> get props => <Object?>[label, value, trend];
} }

View File

@@ -1,12 +1,7 @@
// 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:equatable/equatable.dart';
class ReportsSummary extends Equatable { class ReportsSummary extends Equatable {
final double totalHours;
final double otHours;
final double totalSpend;
final double fillRate;
final double avgFillTimeHours;
final double noShowRate;
const ReportsSummary({ const ReportsSummary({
required this.totalHours, required this.totalHours,
@@ -16,9 +11,15 @@ class ReportsSummary extends Equatable {
required this.avgFillTimeHours, required this.avgFillTimeHours,
required this.noShowRate, required this.noShowRate,
}); });
final double totalHours;
final double otHours;
final double totalSpend;
final double fillRate;
final double avgFillTimeHours;
final double noShowRate;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
totalHours, totalHours,
otHours, otHours,
totalSpend, totalSpend,
@@ -27,3 +28,4 @@ class ReportsSummary extends Equatable {
noShowRate, noShowRate,
]; ];
} }

View File

@@ -1,14 +1,7 @@
// 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:equatable/equatable.dart';
class SpendReport extends Equatable { class SpendReport extends Equatable {
final double totalSpend;
final double averageCost;
final int paidInvoices;
final int pendingInvoices;
final int overdueInvoices;
final List<SpendInvoice> invoices;
final List<SpendChartPoint> chartData;
final List<SpendIndustryCategory> industryBreakdown;
const SpendReport({ const SpendReport({
required this.totalSpend, required this.totalSpend,
@@ -20,9 +13,17 @@ class SpendReport extends Equatable {
required this.chartData, required this.chartData,
required this.industryBreakdown, required this.industryBreakdown,
}); });
final double totalSpend;
final double averageCost;
final int paidInvoices;
final int pendingInvoices;
final int overdueInvoices;
final List<SpendInvoice> invoices;
final List<SpendChartPoint> chartData;
final List<SpendIndustryCategory> industryBreakdown;
@override @override
List<Object?> get props => [ List<Object?> get props => <Object?>[
totalSpend, totalSpend,
averageCost, averageCost,
paidInvoices, paidInvoices,
@@ -35,28 +36,21 @@ class SpendReport extends Equatable {
} }
class SpendIndustryCategory extends Equatable { class SpendIndustryCategory extends Equatable {
final String name;
final double amount;
final double percentage;
const SpendIndustryCategory({ const SpendIndustryCategory({
required this.name, required this.name,
required this.amount, required this.amount,
required this.percentage, required this.percentage,
}); });
final String name;
final double amount;
final double percentage;
@override @override
List<Object?> get props => [name, amount, percentage]; List<Object?> get props => <Object?>[name, amount, percentage];
} }
class SpendInvoice extends Equatable { class SpendInvoice extends Equatable {
final String id;
final String invoiceNumber;
final DateTime issueDate;
final double amount;
final String status;
final String vendorName;
final String? industry;
const SpendInvoice({ const SpendInvoice({
required this.id, required this.id,
@@ -67,17 +61,25 @@ class SpendInvoice extends Equatable {
required this.vendorName, required this.vendorName,
this.industry, this.industry,
}); });
final String id;
final String invoiceNumber;
final DateTime issueDate;
final double amount;
final String status;
final String vendorName;
final String? industry;
@override @override
List<Object?> get props => [id, invoiceNumber, issueDate, amount, status, vendorName, industry]; List<Object?> get props => <Object?>[id, invoiceNumber, issueDate, amount, status, vendorName, industry];
} }
class SpendChartPoint extends Equatable { class SpendChartPoint extends Equatable {
const SpendChartPoint({required this.date, required this.amount});
final DateTime date; final DateTime date;
final double amount; final double amount;
const SpendChartPoint({required this.date, required this.amount});
@override @override
List<Object?> get props => [date, amount]; List<Object?> get props => <Object?>[date, amount];
} }

View File

@@ -2,35 +2,6 @@ import 'package:equatable/equatable.dart';
import 'package:krow_domain/src/entities/shifts/break/break.dart'; import 'package:krow_domain/src/entities/shifts/break/break.dart';
class Shift extends Equatable { class Shift extends Equatable {
final String id;
final String title;
final String clientName;
final String? logoUrl;
final double hourlyRate;
final String location;
final String locationAddress;
final String date;
final String startTime;
final String endTime;
final String createdDate;
final bool? tipsAvailable;
final bool? travelTime;
final bool? mealProvided;
final bool? parkingAvailable;
final bool? gasCompensation;
final String? description;
final String? instructions;
final List<ShiftManager>? managers;
final double? latitude;
final double? longitude;
final String? status;
final int? durationDays; // For multi-day shifts
final int? requiredSlots;
final int? filledSlots;
final String? roleId;
final bool? hasApplied;
final double? totalValue;
final Break? breakInfo;
const Shift({ const Shift({
required this.id, required this.id,
@@ -63,6 +34,35 @@ class Shift extends Equatable {
this.totalValue, this.totalValue,
this.breakInfo, this.breakInfo,
}); });
final String id;
final String title;
final String clientName;
final String? logoUrl;
final double hourlyRate;
final String location;
final String locationAddress;
final String date;
final String startTime;
final String endTime;
final String createdDate;
final bool? tipsAvailable;
final bool? travelTime;
final bool? mealProvided;
final bool? parkingAvailable;
final bool? gasCompensation;
final String? description;
final String? instructions;
final List<ShiftManager>? managers;
final double? latitude;
final double? longitude;
final String? status;
final int? durationDays; // For multi-day shifts
final int? requiredSlots;
final int? filledSlots;
final String? roleId;
final bool? hasApplied;
final double? totalValue;
final Break? breakInfo;
@override @override
List<Object?> get props => <Object?>[ List<Object?> get props => <Object?>[

View File

@@ -32,8 +32,8 @@ sealed class AuthException extends AppException {
/// Thrown when email/password combination is incorrect. /// Thrown when email/password combination is incorrect.
class InvalidCredentialsException extends AuthException { class InvalidCredentialsException extends AuthException {
const InvalidCredentialsException({String? technicalMessage}) const InvalidCredentialsException({super.technicalMessage})
: super(code: 'AUTH_001', technicalMessage: technicalMessage); : super(code: 'AUTH_001');
@override @override
String get messageKey => 'errors.auth.invalid_credentials'; String get messageKey => 'errors.auth.invalid_credentials';
@@ -41,8 +41,8 @@ class InvalidCredentialsException extends AuthException {
/// Thrown when attempting to register with an email that already exists. /// Thrown when attempting to register with an email that already exists.
class AccountExistsException extends AuthException { class AccountExistsException extends AuthException {
const AccountExistsException({String? technicalMessage}) const AccountExistsException({super.technicalMessage})
: super(code: 'AUTH_002', technicalMessage: technicalMessage); : super(code: 'AUTH_002');
@override @override
String get messageKey => 'errors.auth.account_exists'; String get messageKey => 'errors.auth.account_exists';
@@ -50,8 +50,8 @@ class AccountExistsException extends AuthException {
/// Thrown when the user session has expired. /// Thrown when the user session has expired.
class SessionExpiredException extends AuthException { class SessionExpiredException extends AuthException {
const SessionExpiredException({String? technicalMessage}) const SessionExpiredException({super.technicalMessage})
: super(code: 'AUTH_003', technicalMessage: technicalMessage); : super(code: 'AUTH_003');
@override @override
String get messageKey => 'errors.auth.session_expired'; String get messageKey => 'errors.auth.session_expired';
@@ -59,8 +59,8 @@ class SessionExpiredException extends AuthException {
/// Thrown when user profile is not found in database after Firebase auth. /// Thrown when user profile is not found in database after Firebase auth.
class UserNotFoundException extends AuthException { class UserNotFoundException extends AuthException {
const UserNotFoundException({String? technicalMessage}) const UserNotFoundException({super.technicalMessage})
: super(code: 'AUTH_004', technicalMessage: technicalMessage); : super(code: 'AUTH_004');
@override @override
String get messageKey => 'errors.auth.user_not_found'; String get messageKey => 'errors.auth.user_not_found';
@@ -68,8 +68,8 @@ class UserNotFoundException extends AuthException {
/// Thrown when user is not authorized for the current app (wrong role). /// Thrown when user is not authorized for the current app (wrong role).
class UnauthorizedAppException extends AuthException { class UnauthorizedAppException extends AuthException {
const UnauthorizedAppException({String? technicalMessage}) const UnauthorizedAppException({super.technicalMessage})
: super(code: 'AUTH_005', technicalMessage: technicalMessage); : super(code: 'AUTH_005');
@override @override
String get messageKey => 'errors.auth.unauthorized_app'; String get messageKey => 'errors.auth.unauthorized_app';
@@ -77,8 +77,8 @@ class UnauthorizedAppException extends AuthException {
/// Thrown when password doesn't meet security requirements. /// Thrown when password doesn't meet security requirements.
class WeakPasswordException extends AuthException { class WeakPasswordException extends AuthException {
const WeakPasswordException({String? technicalMessage}) const WeakPasswordException({super.technicalMessage})
: super(code: 'AUTH_006', technicalMessage: technicalMessage); : super(code: 'AUTH_006');
@override @override
String get messageKey => 'errors.auth.weak_password'; String get messageKey => 'errors.auth.weak_password';
@@ -86,8 +86,8 @@ class WeakPasswordException extends AuthException {
/// Thrown when sign-up process fails. /// Thrown when sign-up process fails.
class SignUpFailedException extends AuthException { class SignUpFailedException extends AuthException {
const SignUpFailedException({String? technicalMessage}) const SignUpFailedException({super.technicalMessage})
: super(code: 'AUTH_007', technicalMessage: technicalMessage); : super(code: 'AUTH_007');
@override @override
String get messageKey => 'errors.auth.sign_up_failed'; String get messageKey => 'errors.auth.sign_up_failed';
@@ -95,8 +95,8 @@ class SignUpFailedException extends AuthException {
/// Thrown when sign-in process fails. /// Thrown when sign-in process fails.
class SignInFailedException extends AuthException { class SignInFailedException extends AuthException {
const SignInFailedException({String? technicalMessage}) const SignInFailedException({super.technicalMessage})
: super(code: 'AUTH_008', technicalMessage: technicalMessage); : super(code: 'AUTH_008');
@override @override
String get messageKey => 'errors.auth.sign_in_failed'; String get messageKey => 'errors.auth.sign_in_failed';
@@ -104,8 +104,8 @@ class SignInFailedException extends AuthException {
/// Thrown when email exists but password doesn't match. /// Thrown when email exists but password doesn't match.
class PasswordMismatchException extends AuthException { class PasswordMismatchException extends AuthException {
const PasswordMismatchException({String? technicalMessage}) const PasswordMismatchException({super.technicalMessage})
: super(code: 'AUTH_009', technicalMessage: technicalMessage); : super(code: 'AUTH_009');
@override @override
String get messageKey => 'errors.auth.password_mismatch'; String get messageKey => 'errors.auth.password_mismatch';
@@ -113,8 +113,8 @@ class PasswordMismatchException extends AuthException {
/// Thrown when account exists only with Google provider (no password). /// Thrown when account exists only with Google provider (no password).
class GoogleOnlyAccountException extends AuthException { class GoogleOnlyAccountException extends AuthException {
const GoogleOnlyAccountException({String? technicalMessage}) const GoogleOnlyAccountException({super.technicalMessage})
: super(code: 'AUTH_010', technicalMessage: technicalMessage); : super(code: 'AUTH_010');
@override @override
String get messageKey => 'errors.auth.google_only_account'; String get messageKey => 'errors.auth.google_only_account';
@@ -131,8 +131,8 @@ sealed class HubException extends AppException {
/// Thrown when attempting to delete a hub that has active orders. /// Thrown when attempting to delete a hub that has active orders.
class HubHasOrdersException extends HubException { class HubHasOrdersException extends HubException {
const HubHasOrdersException({String? technicalMessage}) const HubHasOrdersException({super.technicalMessage})
: super(code: 'HUB_001', technicalMessage: technicalMessage); : super(code: 'HUB_001');
@override @override
String get messageKey => 'errors.hub.has_orders'; String get messageKey => 'errors.hub.has_orders';
@@ -140,8 +140,8 @@ class HubHasOrdersException extends HubException {
/// Thrown when hub is not found. /// Thrown when hub is not found.
class HubNotFoundException extends HubException { class HubNotFoundException extends HubException {
const HubNotFoundException({String? technicalMessage}) const HubNotFoundException({super.technicalMessage})
: super(code: 'HUB_002', technicalMessage: technicalMessage); : super(code: 'HUB_002');
@override @override
String get messageKey => 'errors.hub.not_found'; String get messageKey => 'errors.hub.not_found';
@@ -149,8 +149,8 @@ class HubNotFoundException extends HubException {
/// Thrown when hub creation fails. /// Thrown when hub creation fails.
class HubCreationFailedException extends HubException { class HubCreationFailedException extends HubException {
const HubCreationFailedException({String? technicalMessage}) const HubCreationFailedException({super.technicalMessage})
: super(code: 'HUB_003', technicalMessage: technicalMessage); : super(code: 'HUB_003');
@override @override
String get messageKey => 'errors.hub.creation_failed'; String get messageKey => 'errors.hub.creation_failed';
@@ -167,8 +167,8 @@ sealed class OrderException extends AppException {
/// Thrown when order creation is attempted without a hub. /// Thrown when order creation is attempted without a hub.
class OrderMissingHubException extends OrderException { class OrderMissingHubException extends OrderException {
const OrderMissingHubException({String? technicalMessage}) const OrderMissingHubException({super.technicalMessage})
: super(code: 'ORDER_001', technicalMessage: technicalMessage); : super(code: 'ORDER_001');
@override @override
String get messageKey => 'errors.order.missing_hub'; String get messageKey => 'errors.order.missing_hub';
@@ -176,8 +176,8 @@ class OrderMissingHubException extends OrderException {
/// Thrown when order creation is attempted without a vendor. /// Thrown when order creation is attempted without a vendor.
class OrderMissingVendorException extends OrderException { class OrderMissingVendorException extends OrderException {
const OrderMissingVendorException({String? technicalMessage}) const OrderMissingVendorException({super.technicalMessage})
: super(code: 'ORDER_002', technicalMessage: technicalMessage); : super(code: 'ORDER_002');
@override @override
String get messageKey => 'errors.order.missing_vendor'; String get messageKey => 'errors.order.missing_vendor';
@@ -185,8 +185,8 @@ class OrderMissingVendorException extends OrderException {
/// Thrown when order creation fails. /// Thrown when order creation fails.
class OrderCreationFailedException extends OrderException { class OrderCreationFailedException extends OrderException {
const OrderCreationFailedException({String? technicalMessage}) const OrderCreationFailedException({super.technicalMessage})
: super(code: 'ORDER_003', technicalMessage: technicalMessage); : super(code: 'ORDER_003');
@override @override
String get messageKey => 'errors.order.creation_failed'; String get messageKey => 'errors.order.creation_failed';
@@ -194,8 +194,8 @@ class OrderCreationFailedException extends OrderException {
/// Thrown when shift creation fails. /// Thrown when shift creation fails.
class ShiftCreationFailedException extends OrderException { class ShiftCreationFailedException extends OrderException {
const ShiftCreationFailedException({String? technicalMessage}) const ShiftCreationFailedException({super.technicalMessage})
: super(code: 'ORDER_004', technicalMessage: technicalMessage); : super(code: 'ORDER_004');
@override @override
String get messageKey => 'errors.order.shift_creation_failed'; String get messageKey => 'errors.order.shift_creation_failed';
@@ -203,8 +203,8 @@ class ShiftCreationFailedException extends OrderException {
/// Thrown when order is missing required business context. /// Thrown when order is missing required business context.
class OrderMissingBusinessException extends OrderException { class OrderMissingBusinessException extends OrderException {
const OrderMissingBusinessException({String? technicalMessage}) const OrderMissingBusinessException({super.technicalMessage})
: super(code: 'ORDER_005', technicalMessage: technicalMessage); : super(code: 'ORDER_005');
@override @override
String get messageKey => 'errors.order.missing_business'; String get messageKey => 'errors.order.missing_business';
@@ -221,8 +221,8 @@ sealed class ProfileException extends AppException {
/// Thrown when staff profile is not found. /// Thrown when staff profile is not found.
class StaffProfileNotFoundException extends ProfileException { class StaffProfileNotFoundException extends ProfileException {
const StaffProfileNotFoundException({String? technicalMessage}) const StaffProfileNotFoundException({super.technicalMessage})
: super(code: 'PROFILE_001', technicalMessage: technicalMessage); : super(code: 'PROFILE_001');
@override @override
String get messageKey => 'errors.profile.staff_not_found'; String get messageKey => 'errors.profile.staff_not_found';
@@ -230,8 +230,8 @@ class StaffProfileNotFoundException extends ProfileException {
/// Thrown when business profile is not found. /// Thrown when business profile is not found.
class BusinessNotFoundException extends ProfileException { class BusinessNotFoundException extends ProfileException {
const BusinessNotFoundException({String? technicalMessage}) const BusinessNotFoundException({super.technicalMessage})
: super(code: 'PROFILE_002', technicalMessage: technicalMessage); : super(code: 'PROFILE_002');
@override @override
String get messageKey => 'errors.profile.business_not_found'; String get messageKey => 'errors.profile.business_not_found';
@@ -239,8 +239,8 @@ class BusinessNotFoundException extends ProfileException {
/// Thrown when profile update fails. /// Thrown when profile update fails.
class ProfileUpdateFailedException extends ProfileException { class ProfileUpdateFailedException extends ProfileException {
const ProfileUpdateFailedException({String? technicalMessage}) const ProfileUpdateFailedException({super.technicalMessage})
: super(code: 'PROFILE_003', technicalMessage: technicalMessage); : super(code: 'PROFILE_003');
@override @override
String get messageKey => 'errors.profile.update_failed'; String get messageKey => 'errors.profile.update_failed';
@@ -257,8 +257,8 @@ sealed class ShiftException extends AppException {
/// Thrown when no open roles are available for a shift. /// Thrown when no open roles are available for a shift.
class NoOpenRolesException extends ShiftException { class NoOpenRolesException extends ShiftException {
const NoOpenRolesException({String? technicalMessage}) const NoOpenRolesException({super.technicalMessage})
: super(code: 'SHIFT_001', technicalMessage: technicalMessage); : super(code: 'SHIFT_001');
@override @override
String get messageKey => 'errors.shift.no_open_roles'; String get messageKey => 'errors.shift.no_open_roles';
@@ -266,8 +266,8 @@ class NoOpenRolesException extends ShiftException {
/// Thrown when application for shift is not found. /// Thrown when application for shift is not found.
class ApplicationNotFoundException extends ShiftException { class ApplicationNotFoundException extends ShiftException {
const ApplicationNotFoundException({String? technicalMessage}) const ApplicationNotFoundException({super.technicalMessage})
: super(code: 'SHIFT_002', technicalMessage: technicalMessage); : super(code: 'SHIFT_002');
@override @override
String get messageKey => 'errors.shift.application_not_found'; String get messageKey => 'errors.shift.application_not_found';
@@ -275,8 +275,8 @@ class ApplicationNotFoundException extends ShiftException {
/// Thrown when no active shift is found for clock out. /// Thrown when no active shift is found for clock out.
class NoActiveShiftException extends ShiftException { class NoActiveShiftException extends ShiftException {
const NoActiveShiftException({String? technicalMessage}) const NoActiveShiftException({super.technicalMessage})
: super(code: 'SHIFT_003', technicalMessage: technicalMessage); : super(code: 'SHIFT_003');
@override @override
String get messageKey => 'errors.shift.no_active_shift'; String get messageKey => 'errors.shift.no_active_shift';
@@ -288,8 +288,8 @@ class NoActiveShiftException extends ShiftException {
/// Thrown when there is no network connection. /// Thrown when there is no network connection.
class NetworkException extends AppException { class NetworkException extends AppException {
const NetworkException({String? technicalMessage}) const NetworkException({super.technicalMessage})
: super(code: 'NET_001', technicalMessage: technicalMessage); : super(code: 'NET_001');
@override @override
String get messageKey => 'errors.generic.no_connection'; String get messageKey => 'errors.generic.no_connection';
@@ -297,8 +297,8 @@ class NetworkException extends AppException {
/// Thrown when an unexpected error occurs. /// Thrown when an unexpected error occurs.
class UnknownException extends AppException { class UnknownException extends AppException {
const UnknownException({String? technicalMessage}) const UnknownException({super.technicalMessage})
: super(code: 'UNKNOWN', technicalMessage: technicalMessage); : super(code: 'UNKNOWN');
@override @override
String get messageKey => 'errors.generic.unknown'; String get messageKey => 'errors.generic.unknown';
@@ -306,8 +306,8 @@ class UnknownException extends AppException {
/// Thrown when the server returns an error (500, etc.). /// Thrown when the server returns an error (500, etc.).
class ServerException extends AppException { class ServerException extends AppException {
const ServerException({String? technicalMessage}) const ServerException({super.technicalMessage})
: super(code: 'SRV_001', technicalMessage: technicalMessage); : super(code: 'SRV_001');
@override @override
String get messageKey => 'errors.generic.server_error'; String get messageKey => 'errors.generic.server_error';
@@ -315,8 +315,8 @@ class ServerException extends AppException {
/// Thrown when the service is unavailable (Data Connect down). /// Thrown when the service is unavailable (Data Connect down).
class ServiceUnavailableException extends AppException { class ServiceUnavailableException extends AppException {
const ServiceUnavailableException({String? technicalMessage}) const ServiceUnavailableException({super.technicalMessage})
: super(code: 'SRV_002', technicalMessage: technicalMessage); : super(code: 'SRV_002');
@override @override
String get messageKey => 'errors.generic.service_unavailable'; String get messageKey => 'errors.generic.service_unavailable';
@@ -324,8 +324,8 @@ class ServiceUnavailableException extends AppException {
/// Thrown when user is not authenticated. /// Thrown when user is not authenticated.
class NotAuthenticatedException extends AppException { class NotAuthenticatedException extends AppException {
const NotAuthenticatedException({String? technicalMessage}) const NotAuthenticatedException({super.technicalMessage})
: super(code: 'AUTH_NOT_LOGGED', technicalMessage: technicalMessage); : super(code: 'AUTH_NOT_LOGGED');
@override @override
String get messageKey => 'errors.auth.not_authenticated'; String get messageKey => 'errors.auth.not_authenticated';

View File

@@ -55,7 +55,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
emit(state.copyWith(status: ClientAuthStatus.loading)); emit(state.copyWith(status: ClientAuthStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final User user = await _signInWithEmail( final User user = await _signInWithEmail(
SignInWithEmailArguments(email: event.email, password: event.password), SignInWithEmailArguments(email: event.email, password: event.password),
@@ -77,7 +77,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
emit(state.copyWith(status: ClientAuthStatus.loading)); emit(state.copyWith(status: ClientAuthStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final User user = await _signUpWithEmail( final User user = await _signUpWithEmail(
SignUpWithEmailArguments( SignUpWithEmailArguments(
@@ -103,7 +103,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
emit(state.copyWith(status: ClientAuthStatus.loading)); emit(state.copyWith(status: ClientAuthStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final User user = await _signInWithSocial( final User user = await _signInWithSocial(
SignInWithSocialArguments(provider: event.provider), SignInWithSocialArguments(provider: event.provider),
@@ -125,7 +125,7 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
emit(state.copyWith(status: ClientAuthStatus.loading)); emit(state.copyWith(status: ClientAuthStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _signOut(); await _signOut();
emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null)); emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null));

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/billing_repository.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 /// This implementation follows the "Buffer Layer" pattern by using a dedicated
/// connector repository from the data_connect package. /// connector repository from the data_connect package.
class BillingRepositoryImpl implements BillingRepository { class BillingRepositoryImpl implements BillingRepository {
final dc.BillingConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
BillingRepositoryImpl({ BillingRepositoryImpl({
dc.BillingConnectorRepository? connectorRepository, dc.BillingConnectorRepository? connectorRepository,
@@ -16,28 +15,30 @@ class BillingRepositoryImpl implements BillingRepository {
}) : _connectorRepository = connectorRepository ?? }) : _connectorRepository = connectorRepository ??
dc.DataConnectService.instance.getBillingRepository(), dc.DataConnectService.instance.getBillingRepository(),
_service = service ?? dc.DataConnectService.instance; _service = service ?? dc.DataConnectService.instance;
final dc.BillingConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
@override @override
Future<List<BusinessBankAccount>> getBankAccounts() async { Future<List<BusinessBankAccount>> getBankAccounts() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getBankAccounts(businessId: businessId); return _connectorRepository.getBankAccounts(businessId: businessId);
} }
@override @override
Future<double> getCurrentBillAmount() async { Future<double> getCurrentBillAmount() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getCurrentBillAmount(businessId: businessId); return _connectorRepository.getCurrentBillAmount(businessId: businessId);
} }
@override @override
Future<List<Invoice>> getInvoiceHistory() async { Future<List<Invoice>> getInvoiceHistory() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getInvoiceHistory(businessId: businessId); return _connectorRepository.getInvoiceHistory(businessId: businessId);
} }
@override @override
Future<List<Invoice>> getPendingInvoices() async { Future<List<Invoice>> getPendingInvoices() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getPendingInvoices(businessId: businessId); return _connectorRepository.getPendingInvoices(businessId: businessId);
} }
@@ -49,10 +50,11 @@ class BillingRepositoryImpl implements BillingRepository {
@override @override
Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async { Future<List<InvoiceItem>> getSpendingBreakdown(BillingPeriod period) async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getSpendingBreakdown( return _connectorRepository.getSpendingBreakdown(
businessId: businessId, businessId: businessId,
period: period, period: period,
); );
} }
} }

View File

@@ -47,7 +47,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
) async { ) async {
emit(state.copyWith(status: BillingStatus.loading)); emit(state.copyWith(status: BillingStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final List<dynamic> results = final List<dynamic> results =
await Future.wait<dynamic>(<Future<dynamic>>[ await Future.wait<dynamic>(<Future<dynamic>>[
@@ -102,7 +102,7 @@ class BillingBloc extends Bloc<BillingEvent, BillingState>
Emitter<BillingState> emit, Emitter<BillingState> emit,
) async { ) async {
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final List<InvoiceItem> spendingItems = final List<InvoiceItem> spendingItems =
await _getSpendingBreakdown.call(event.period); await _getSpendingBreakdown.call(event.period);

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/coverage_repository.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 /// This implementation follows the "Buffer Layer" pattern by using a dedicated
/// connector repository from the data_connect package. /// connector repository from the data_connect package.
class CoverageRepositoryImpl implements CoverageRepository { class CoverageRepositoryImpl implements CoverageRepository {
final dc.CoverageConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
CoverageRepositoryImpl({ CoverageRepositoryImpl({
dc.CoverageConnectorRepository? connectorRepository, dc.CoverageConnectorRepository? connectorRepository,
@@ -16,10 +15,12 @@ class CoverageRepositoryImpl implements CoverageRepository {
}) : _connectorRepository = connectorRepository ?? }) : _connectorRepository = connectorRepository ??
dc.DataConnectService.instance.getCoverageRepository(), dc.DataConnectService.instance.getCoverageRepository(),
_service = service ?? dc.DataConnectService.instance; _service = service ?? dc.DataConnectService.instance;
final dc.CoverageConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
@override @override
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async { Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getShiftsForDate( return _connectorRepository.getShiftsForDate(
businessId: businessId, businessId: businessId,
date: date, date: date,
@@ -58,3 +59,4 @@ class CoverageRepositoryImpl implements CoverageRepository {
); );
} }
} }

View File

@@ -43,7 +43,7 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
); );
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
// Fetch shifts and stats concurrently // Fetch shifts and stats concurrently
final List<Object> results = await Future.wait<Object>(<Future<Object>>[ final List<Object> results = await Future.wait<Object>(<Future<Object>>[

View File

@@ -36,7 +36,7 @@ class ClientMainBottomBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Translations.of(context); final Translations t = Translations.of(context);
// Client App colors from design system // Client App colors from design system
const Color activeColor = UiColors.textPrimary; const Color activeColor = UiColors.textPrimary;
const Color inactiveColor = UiColors.textInactive; const Color inactiveColor = UiColors.textInactive;

View File

@@ -20,7 +20,7 @@ class ClientCreateOrderBloc
Emitter<ClientCreateOrderState> emit, Emitter<ClientCreateOrderState> emit,
) async { ) async {
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final List<OrderType> types = await _getOrderTypesUseCase(); final List<OrderType> types = await _getOrderTypesUseCase();
emit(ClientCreateOrderLoadSuccess(types)); emit(ClientCreateOrderLoadSuccess(types));

View File

@@ -220,7 +220,7 @@ class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState>
) async { ) async {
emit(state.copyWith(status: OneTimeOrderStatus.loading)); emit(state.copyWith(status: OneTimeOrderStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final Map<String, double> roleRates = <String, double>{ final Map<String, double> roleRates = <String, double>{
for (final OneTimeOrderRoleOption role in state.roles) for (final OneTimeOrderRoleOption role in state.roles)

View File

@@ -272,7 +272,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
) async { ) async {
emit(state.copyWith(status: PermanentOrderStatus.loading)); emit(state.copyWith(status: PermanentOrderStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final Map<String, double> roleRates = <String, double>{ final Map<String, double> roleRates = <String, double>{
for (final PermanentOrderRoleOption role in state.roles) for (final PermanentOrderRoleOption role in state.roles)
@@ -280,7 +280,7 @@ class PermanentOrderBloc extends Bloc<PermanentOrderEvent, PermanentOrderState>
}; };
final PermanentOrderHubOption? selectedHub = state.selectedHub; final PermanentOrderHubOption? selectedHub = state.selectedHub;
if (selectedHub == null) { if (selectedHub == null) {
throw domain.OrderMissingHubException(); throw const domain.OrderMissingHubException();
} }
final domain.PermanentOrder order = domain.PermanentOrder( final domain.PermanentOrder order = domain.PermanentOrder(
startDate: state.startDate, startDate: state.startDate,

View File

@@ -69,7 +69,7 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
emit(const RapidOrderSubmitting()); emit(const RapidOrderSubmitting());
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _createRapidOrderUseCase( await _createRapidOrderUseCase(
RapidOrderArguments(description: message), RapidOrderArguments(description: message),

View File

@@ -289,7 +289,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
) async { ) async {
emit(state.copyWith(status: RecurringOrderStatus.loading)); emit(state.copyWith(status: RecurringOrderStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final Map<String, double> roleRates = <String, double>{ final Map<String, double> roleRates = <String, double>{
for (final RecurringOrderRoleOption role in state.roles) for (final RecurringOrderRoleOption role in state.roles)
@@ -297,7 +297,7 @@ class RecurringOrderBloc extends Bloc<RecurringOrderEvent, RecurringOrderState>
}; };
final RecurringOrderHubOption? selectedHub = state.selectedHub; final RecurringOrderHubOption? selectedHub = state.selectedHub;
if (selectedHub == null) { if (selectedHub == null) {
throw domain.OrderMissingHubException(); throw const domain.OrderMissingHubException();
} }
final domain.RecurringOrder order = domain.RecurringOrder( final domain.RecurringOrder order = domain.RecurringOrder(
startDate: state.startDate, startDate: state.startDate,

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/home_repository_interface.dart'; import '../../domain/repositories/home_repository_interface.dart';
@@ -7,8 +9,6 @@ import '../../domain/repositories/home_repository_interface.dart';
/// This implementation follows the "Buffer Layer" pattern by using a dedicated /// This implementation follows the "Buffer Layer" pattern by using a dedicated
/// connector repository from the data_connect package. /// connector repository from the data_connect package.
class HomeRepositoryImpl implements HomeRepositoryInterface { class HomeRepositoryImpl implements HomeRepositoryInterface {
final dc.HomeConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
HomeRepositoryImpl({ HomeRepositoryImpl({
dc.HomeConnectorRepository? connectorRepository, dc.HomeConnectorRepository? connectorRepository,
@@ -16,10 +16,12 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
}) : _connectorRepository = connectorRepository ?? }) : _connectorRepository = connectorRepository ??
dc.DataConnectService.instance.getHomeRepository(), dc.DataConnectService.instance.getHomeRepository(),
_service = service ?? dc.DataConnectService.instance; _service = service ?? dc.DataConnectService.instance;
final dc.HomeConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
@override @override
Future<HomeDashboardData> getDashboardData() async { Future<HomeDashboardData> getDashboardData() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getDashboardData(businessId: businessId); return _connectorRepository.getDashboardData(businessId: businessId);
} }
@@ -37,16 +39,16 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
return await _service.run(() async { return await _service.run(() async {
final String businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
final businessResult = await _service.connector final QueryResult<dc.GetBusinessByIdData, dc.GetBusinessByIdVariables> businessResult = await _service.connector
.getBusinessById(id: businessId) .getBusinessById(id: businessId)
.execute(); .execute();
final b = businessResult.data.business; final dc.GetBusinessByIdBusiness? b = businessResult.data.business;
if (b == null) { if (b == null) {
throw Exception('Business data not found for ID: $businessId'); throw Exception('Business data not found for ID: $businessId');
} }
final updatedSession = dc.ClientSession( final dc.ClientSession updatedSession = dc.ClientSession(
business: dc.ClientBusinessSession( business: dc.ClientBusinessSession(
id: b.id, id: b.id,
businessName: b.businessName, businessName: b.businessName,
@@ -67,7 +69,8 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
@override @override
Future<List<ReorderItem>> getRecentReorders() async { Future<List<ReorderItem>> getRecentReorders() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getRecentReorders(businessId: businessId); return _connectorRepository.getRecentReorders(businessId: businessId);
} }
} }

View File

@@ -37,7 +37,7 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState>
) async { ) async {
emit(state.copyWith(status: ClientHomeStatus.loading)); emit(state.copyWith(status: ClientHomeStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
// Get session data // Get session data
final UserSessionData sessionData = await _getUserSessionDataUseCase(); final UserSessionData sessionData = await _getUserSessionDataUseCase();

View File

@@ -651,9 +651,9 @@ class _ShiftOrderFormSheetState extends State<ShiftOrderFormSheet> {
return Container( return Container(
height: MediaQuery.of(context).size.height * 0.95, height: MediaQuery.of(context).size.height * 0.95,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgPrimary, color: UiColors.bgPrimary,
borderRadius: const BorderRadius.vertical(top: Radius.circular(UiConstants.space6)), borderRadius: BorderRadius.vertical(top: Radius.circular(UiConstants.space6)),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[

View File

@@ -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_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/hub_repository_interface.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 /// This implementation follows the "Buffer Layer" pattern by using a dedicated
/// connector repository from the data_connect package. /// connector repository from the data_connect package.
class HubRepositoryImpl implements HubRepositoryInterface { class HubRepositoryImpl implements HubRepositoryInterface {
final dc.HubsConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
HubRepositoryImpl({ HubRepositoryImpl({
dc.HubsConnectorRepository? connectorRepository, dc.HubsConnectorRepository? connectorRepository,
@@ -16,10 +15,12 @@ class HubRepositoryImpl implements HubRepositoryInterface {
}) : _connectorRepository = connectorRepository ?? }) : _connectorRepository = connectorRepository ??
dc.DataConnectService.instance.getHubsRepository(), dc.DataConnectService.instance.getHubsRepository(),
_service = service ?? dc.DataConnectService.instance; _service = service ?? dc.DataConnectService.instance;
final dc.HubsConnectorRepository _connectorRepository;
final dc.DataConnectService _service;
@override @override
Future<List<Hub>> getHubs() async { Future<List<Hub>> getHubs() async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.getHubs(businessId: businessId); return _connectorRepository.getHubs(businessId: businessId);
} }
@@ -36,7 +37,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
String? country, String? country,
String? zipCode, String? zipCode,
}) async { }) async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.createHub( return _connectorRepository.createHub(
businessId: businessId, businessId: businessId,
name: name, name: name,
@@ -54,7 +55,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
@override @override
Future<void> deleteHub(String id) async { Future<void> deleteHub(String id) async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.deleteHub(businessId: businessId, id: id); return _connectorRepository.deleteHub(businessId: businessId, id: id);
} }
@@ -79,7 +80,7 @@ class HubRepositoryImpl implements HubRepositoryInterface {
String? country, String? country,
String? zipCode, String? zipCode,
}) async { }) async {
final businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
return _connectorRepository.updateHub( return _connectorRepository.updateHub(
businessId: businessId, businessId: businessId,
id: id, id: id,
@@ -96,3 +97,4 @@ class HubRepositoryImpl implements HubRepositoryInterface {
); );
} }
} }

View File

@@ -73,7 +73,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.loading)); emit(state.copyWith(status: ClientHubsStatus.loading));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
final List<Hub> hubs = await _getHubsUseCase(); final List<Hub> hubs = await _getHubsUseCase();
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs)); emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
@@ -92,7 +92,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.actionInProgress)); emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _createHubUseCase( await _createHubUseCase(
CreateHubArguments( CreateHubArguments(
@@ -132,7 +132,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.actionInProgress)); emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _updateHubUseCase( await _updateHubUseCase(
UpdateHubArguments( UpdateHubArguments(
@@ -172,7 +172,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.actionInProgress)); emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId)); await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId));
final List<Hub> hubs = await _getHubsUseCase(); final List<Hub> hubs = await _getHubsUseCase();
@@ -198,7 +198,7 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
emit(state.copyWith(status: ClientHubsStatus.actionInProgress)); emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
await handleError( await handleError(
emit: emit, emit: emit.call,
action: () async { action: () async {
await _assignNfcTagUseCase( await _assignNfcTagUseCase(
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId), AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),

View File

@@ -7,10 +7,10 @@ import '../../domain/repositories/reports_repository.dart';
/// This implementation follows the "Buffer Layer" pattern by using a dedicated /// This implementation follows the "Buffer Layer" pattern by using a dedicated
/// connector repository from the data_connect package. /// connector repository from the data_connect package.
class ReportsRepositoryImpl implements ReportsRepository { class ReportsRepositoryImpl implements ReportsRepository {
final ReportsConnectorRepository _connectorRepository;
ReportsRepositoryImpl({ReportsConnectorRepository? connectorRepository}) ReportsRepositoryImpl({ReportsConnectorRepository? connectorRepository})
: _connectorRepository = connectorRepository ?? DataConnectService.instance.getReportsRepository(); : _connectorRepository = connectorRepository ?? DataConnectService.instance.getReportsRepository();
final ReportsConnectorRepository _connectorRepository;
@override @override
Future<DailyOpsReport> getDailyOpsReport({ Future<DailyOpsReport> getDailyOpsReport({

View File

@@ -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:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/src/entities/reports/coverage_report.dart';
import '../../../domain/repositories/reports_repository.dart'; import '../../../domain/repositories/reports_repository.dart';
import 'coverage_event.dart'; import 'coverage_event.dart';
import 'coverage_state.dart'; import 'coverage_state.dart';
class CoverageBloc extends Bloc<CoverageEvent, CoverageState> { class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
final ReportsRepository _reportsRepository;
CoverageBloc({required ReportsRepository reportsRepository}) CoverageBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(CoverageInitial()) { super(CoverageInitial()) {
on<LoadCoverageReport>(_onLoadCoverageReport); on<LoadCoverageReport>(_onLoadCoverageReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadCoverageReport( Future<void> _onLoadCoverageReport(
LoadCoverageReport event, LoadCoverageReport event,
@@ -18,7 +20,7 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
) async { ) async {
emit(CoverageLoading()); emit(CoverageLoading());
try { try {
final report = await _reportsRepository.getCoverageReport( final CoverageReport report = await _reportsRepository.getCoverageReport(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,
@@ -29,3 +31,4 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState> {
} }
} }
} }

View File

@@ -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'; import 'package:equatable/equatable.dart';
abstract class CoverageEvent extends Equatable { abstract class CoverageEvent extends Equatable {
const CoverageEvent(); const CoverageEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadCoverageReport extends CoverageEvent { class LoadCoverageReport extends CoverageEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadCoverageReport({ const LoadCoverageReport({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class CoverageState extends Equatable {
const CoverageState(); const CoverageState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class CoverageInitial extends CoverageState {} class CoverageInitial extends CoverageState {}
@@ -13,19 +14,20 @@ class CoverageInitial extends CoverageState {}
class CoverageLoading extends CoverageState {} class CoverageLoading extends CoverageState {}
class CoverageLoaded extends CoverageState { class CoverageLoaded extends CoverageState {
final CoverageReport report;
const CoverageLoaded(this.report); const CoverageLoaded(this.report);
final CoverageReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class CoverageError extends CoverageState { class CoverageError extends CoverageState {
final String message;
const CoverageError(this.message); const CoverageError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; 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 '../../../domain/repositories/reports_repository.dart';
import 'daily_ops_event.dart'; import 'daily_ops_event.dart';
import 'daily_ops_state.dart'; import 'daily_ops_state.dart';
class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState> { class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState> {
final ReportsRepository _reportsRepository;
DailyOpsBloc({required ReportsRepository reportsRepository}) DailyOpsBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(DailyOpsInitial()) { super(DailyOpsInitial()) {
on<LoadDailyOpsReport>(_onLoadDailyOpsReport); on<LoadDailyOpsReport>(_onLoadDailyOpsReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadDailyOpsReport( Future<void> _onLoadDailyOpsReport(
LoadDailyOpsReport event, LoadDailyOpsReport event,
@@ -18,7 +19,7 @@ class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState> {
) async { ) async {
emit(DailyOpsLoading()); emit(DailyOpsLoading());
try { try {
final report = await _reportsRepository.getDailyOpsReport( final DailyOpsReport report = await _reportsRepository.getDailyOpsReport(
businessId: event.businessId, businessId: event.businessId,
date: event.date, date: event.date,
); );

View File

@@ -4,18 +4,18 @@ abstract class DailyOpsEvent extends Equatable {
const DailyOpsEvent(); const DailyOpsEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadDailyOpsReport extends DailyOpsEvent { class LoadDailyOpsReport extends DailyOpsEvent {
final String? businessId;
final DateTime date;
const LoadDailyOpsReport({ const LoadDailyOpsReport({
this.businessId, this.businessId,
required this.date, required this.date,
}); });
final String? businessId;
final DateTime date;
@override @override
List<Object?> get props => [businessId, date]; List<Object?> get props => <Object?>[businessId, date];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class DailyOpsState extends Equatable {
const DailyOpsState(); const DailyOpsState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class DailyOpsInitial extends DailyOpsState {} class DailyOpsInitial extends DailyOpsState {}
@@ -13,19 +14,20 @@ class DailyOpsInitial extends DailyOpsState {}
class DailyOpsLoading extends DailyOpsState {} class DailyOpsLoading extends DailyOpsState {}
class DailyOpsLoaded extends DailyOpsState { class DailyOpsLoaded extends DailyOpsState {
final DailyOpsReport report;
const DailyOpsLoaded(this.report); const DailyOpsLoaded(this.report);
final DailyOpsReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class DailyOpsError extends DailyOpsState { class DailyOpsError extends DailyOpsState {
final String message;
const DailyOpsError(this.message); const DailyOpsError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/src/entities/reports/forecast_report.dart';
import '../../../domain/repositories/reports_repository.dart'; import '../../../domain/repositories/reports_repository.dart';
import 'forecast_event.dart'; import 'forecast_event.dart';
import 'forecast_state.dart'; import 'forecast_state.dart';
class ForecastBloc extends Bloc<ForecastEvent, ForecastState> { class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
final ReportsRepository _reportsRepository;
ForecastBloc({required ReportsRepository reportsRepository}) ForecastBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(ForecastInitial()) { super(ForecastInitial()) {
on<LoadForecastReport>(_onLoadForecastReport); on<LoadForecastReport>(_onLoadForecastReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadForecastReport( Future<void> _onLoadForecastReport(
LoadForecastReport event, LoadForecastReport event,
@@ -18,7 +19,7 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState> {
) async { ) async {
emit(ForecastLoading()); emit(ForecastLoading());
try { try {
final report = await _reportsRepository.getForecastReport( final ForecastReport report = await _reportsRepository.getForecastReport(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,

View File

@@ -4,20 +4,20 @@ abstract class ForecastEvent extends Equatable {
const ForecastEvent(); const ForecastEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadForecastReport extends ForecastEvent { class LoadForecastReport extends ForecastEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadForecastReport({ const LoadForecastReport({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class ForecastState extends Equatable {
const ForecastState(); const ForecastState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class ForecastInitial extends ForecastState {} class ForecastInitial extends ForecastState {}
@@ -13,19 +14,20 @@ class ForecastInitial extends ForecastState {}
class ForecastLoading extends ForecastState {} class ForecastLoading extends ForecastState {}
class ForecastLoaded extends ForecastState { class ForecastLoaded extends ForecastState {
final ForecastReport report;
const ForecastLoaded(this.report); const ForecastLoaded(this.report);
final ForecastReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class ForecastError extends ForecastState { class ForecastError extends ForecastState {
final String message;
const ForecastError(this.message); const ForecastError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; 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 '../../../domain/repositories/reports_repository.dart';
import 'no_show_event.dart'; import 'no_show_event.dart';
import 'no_show_state.dart'; import 'no_show_state.dart';
class NoShowBloc extends Bloc<NoShowEvent, NoShowState> { class NoShowBloc extends Bloc<NoShowEvent, NoShowState> {
final ReportsRepository _reportsRepository;
NoShowBloc({required ReportsRepository reportsRepository}) NoShowBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(NoShowInitial()) { super(NoShowInitial()) {
on<LoadNoShowReport>(_onLoadNoShowReport); on<LoadNoShowReport>(_onLoadNoShowReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadNoShowReport( Future<void> _onLoadNoShowReport(
LoadNoShowReport event, LoadNoShowReport event,
@@ -18,7 +19,7 @@ class NoShowBloc extends Bloc<NoShowEvent, NoShowState> {
) async { ) async {
emit(NoShowLoading()); emit(NoShowLoading());
try { try {
final report = await _reportsRepository.getNoShowReport( final NoShowReport report = await _reportsRepository.getNoShowReport(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,

View File

@@ -4,20 +4,20 @@ abstract class NoShowEvent extends Equatable {
const NoShowEvent(); const NoShowEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadNoShowReport extends NoShowEvent { class LoadNoShowReport extends NoShowEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadNoShowReport({ const LoadNoShowReport({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class NoShowState extends Equatable {
const NoShowState(); const NoShowState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class NoShowInitial extends NoShowState {} class NoShowInitial extends NoShowState {}
@@ -13,19 +14,20 @@ class NoShowInitial extends NoShowState {}
class NoShowLoading extends NoShowState {} class NoShowLoading extends NoShowState {}
class NoShowLoaded extends NoShowState { class NoShowLoaded extends NoShowState {
final NoShowReport report;
const NoShowLoaded(this.report); const NoShowLoaded(this.report);
final NoShowReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class NoShowError extends NoShowState { class NoShowError extends NoShowState {
final String message;
const NoShowError(this.message); const NoShowError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/src/entities/reports/performance_report.dart';
import '../../../domain/repositories/reports_repository.dart'; import '../../../domain/repositories/reports_repository.dart';
import 'performance_event.dart'; import 'performance_event.dart';
import 'performance_state.dart'; import 'performance_state.dart';
class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState> { class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState> {
final ReportsRepository _reportsRepository;
PerformanceBloc({required ReportsRepository reportsRepository}) PerformanceBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(PerformanceInitial()) { super(PerformanceInitial()) {
on<LoadPerformanceReport>(_onLoadPerformanceReport); on<LoadPerformanceReport>(_onLoadPerformanceReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadPerformanceReport( Future<void> _onLoadPerformanceReport(
LoadPerformanceReport event, LoadPerformanceReport event,
@@ -18,7 +19,7 @@ class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState> {
) async { ) async {
emit(PerformanceLoading()); emit(PerformanceLoading());
try { try {
final report = await _reportsRepository.getPerformanceReport( final PerformanceReport report = await _reportsRepository.getPerformanceReport(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,

View File

@@ -4,20 +4,20 @@ abstract class PerformanceEvent extends Equatable {
const PerformanceEvent(); const PerformanceEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadPerformanceReport extends PerformanceEvent { class LoadPerformanceReport extends PerformanceEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadPerformanceReport({ const LoadPerformanceReport({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class PerformanceState extends Equatable {
const PerformanceState(); const PerformanceState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class PerformanceInitial extends PerformanceState {} class PerformanceInitial extends PerformanceState {}
@@ -13,19 +14,20 @@ class PerformanceInitial extends PerformanceState {}
class PerformanceLoading extends PerformanceState {} class PerformanceLoading extends PerformanceState {}
class PerformanceLoaded extends PerformanceState { class PerformanceLoaded extends PerformanceState {
final PerformanceReport report;
const PerformanceLoaded(this.report); const PerformanceLoaded(this.report);
final PerformanceReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class PerformanceError extends PerformanceState { class PerformanceError extends PerformanceState {
final String message;
const PerformanceError(this.message); const PerformanceError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/src/entities/reports/spend_report.dart';
import '../../../domain/repositories/reports_repository.dart'; import '../../../domain/repositories/reports_repository.dart';
import 'spend_event.dart'; import 'spend_event.dart';
import 'spend_state.dart'; import 'spend_state.dart';
class SpendBloc extends Bloc<SpendEvent, SpendState> { class SpendBloc extends Bloc<SpendEvent, SpendState> {
final ReportsRepository _reportsRepository;
SpendBloc({required ReportsRepository reportsRepository}) SpendBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(SpendInitial()) { super(SpendInitial()) {
on<LoadSpendReport>(_onLoadSpendReport); on<LoadSpendReport>(_onLoadSpendReport);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadSpendReport( Future<void> _onLoadSpendReport(
LoadSpendReport event, LoadSpendReport event,
@@ -18,7 +19,7 @@ class SpendBloc extends Bloc<SpendEvent, SpendState> {
) async { ) async {
emit(SpendLoading()); emit(SpendLoading());
try { try {
final report = await _reportsRepository.getSpendReport( final SpendReport report = await _reportsRepository.getSpendReport(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,

View File

@@ -4,20 +4,20 @@ abstract class SpendEvent extends Equatable {
const SpendEvent(); const SpendEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadSpendReport extends SpendEvent { class LoadSpendReport extends SpendEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadSpendReport({ const LoadSpendReport({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class SpendState extends Equatable {
const SpendState(); const SpendState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class SpendInitial extends SpendState {} class SpendInitial extends SpendState {}
@@ -13,19 +14,20 @@ class SpendInitial extends SpendState {}
class SpendLoading extends SpendState {} class SpendLoading extends SpendState {}
class SpendLoaded extends SpendState { class SpendLoaded extends SpendState {
final SpendReport report;
const SpendLoaded(this.report); const SpendLoaded(this.report);
final SpendReport report;
@override @override
List<Object?> get props => [report]; List<Object?> get props => <Object?>[report];
} }
class SpendError extends SpendState { class SpendError extends SpendState {
final String message;
const SpendError(this.message); const SpendError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -1,16 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_domain/src/entities/reports/reports_summary.dart';
import '../../../domain/repositories/reports_repository.dart'; import '../../../domain/repositories/reports_repository.dart';
import 'reports_summary_event.dart'; import 'reports_summary_event.dart';
import 'reports_summary_state.dart'; import 'reports_summary_state.dart';
class ReportsSummaryBloc extends Bloc<ReportsSummaryEvent, ReportsSummaryState> { class ReportsSummaryBloc extends Bloc<ReportsSummaryEvent, ReportsSummaryState> {
final ReportsRepository _reportsRepository;
ReportsSummaryBloc({required ReportsRepository reportsRepository}) ReportsSummaryBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository, : _reportsRepository = reportsRepository,
super(ReportsSummaryInitial()) { super(ReportsSummaryInitial()) {
on<LoadReportsSummary>(_onLoadReportsSummary); on<LoadReportsSummary>(_onLoadReportsSummary);
} }
final ReportsRepository _reportsRepository;
Future<void> _onLoadReportsSummary( Future<void> _onLoadReportsSummary(
LoadReportsSummary event, LoadReportsSummary event,
@@ -18,7 +19,7 @@ class ReportsSummaryBloc extends Bloc<ReportsSummaryEvent, ReportsSummaryState>
) async { ) async {
emit(ReportsSummaryLoading()); emit(ReportsSummaryLoading());
try { try {
final summary = await _reportsRepository.getReportsSummary( final ReportsSummary summary = await _reportsRepository.getReportsSummary(
businessId: event.businessId, businessId: event.businessId,
startDate: event.startDate, startDate: event.startDate,
endDate: event.endDate, endDate: event.endDate,

View File

@@ -4,20 +4,20 @@ abstract class ReportsSummaryEvent extends Equatable {
const ReportsSummaryEvent(); const ReportsSummaryEvent();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class LoadReportsSummary extends ReportsSummaryEvent { class LoadReportsSummary extends ReportsSummaryEvent {
final String? businessId;
final DateTime startDate;
final DateTime endDate;
const LoadReportsSummary({ const LoadReportsSummary({
this.businessId, this.businessId,
required this.startDate, required this.startDate,
required this.endDate, required this.endDate,
}); });
final String? businessId;
final DateTime startDate;
final DateTime endDate;
@override @override
List<Object?> get props => [businessId, startDate, endDate]; List<Object?> get props => <Object?>[businessId, startDate, endDate];
} }

View File

@@ -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:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
@@ -5,7 +6,7 @@ abstract class ReportsSummaryState extends Equatable {
const ReportsSummaryState(); const ReportsSummaryState();
@override @override
List<Object?> get props => []; List<Object?> get props => <Object?>[];
} }
class ReportsSummaryInitial extends ReportsSummaryState {} class ReportsSummaryInitial extends ReportsSummaryState {}
@@ -13,19 +14,20 @@ class ReportsSummaryInitial extends ReportsSummaryState {}
class ReportsSummaryLoading extends ReportsSummaryState {} class ReportsSummaryLoading extends ReportsSummaryState {}
class ReportsSummaryLoaded extends ReportsSummaryState { class ReportsSummaryLoaded extends ReportsSummaryState {
final ReportsSummary summary;
const ReportsSummaryLoaded(this.summary); const ReportsSummaryLoaded(this.summary);
final ReportsSummary summary;
@override @override
List<Object?> get props => [summary]; List<Object?> get props => <Object?>[summary];
} }
class ReportsSummaryError extends ReportsSummaryState { class ReportsSummaryError extends ReportsSummaryState {
final String message;
const ReportsSummaryError(this.message); const ReportsSummaryError(this.message);
final String message;
@override @override
List<Object?> get props => [message]; List<Object?> get props => <Object?>[message];
} }

View File

@@ -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_bloc.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart'; import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart'; import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart';
@@ -23,12 +24,12 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<CoverageBloc>() create: (BuildContext context) => Modular.get<CoverageBloc>()
..add(LoadCoverageReport(startDate: _startDate, endDate: _endDate)), ..add(LoadCoverageReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<CoverageBloc, CoverageState>( body: BlocBuilder<CoverageBloc, CoverageState>(
builder: (context, state) { builder: (BuildContext context, CoverageState state) {
if (state is CoverageLoading) { if (state is CoverageLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -38,10 +39,10 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
} }
if (state is CoverageLoaded) { if (state is CoverageLoaded) {
final report = state.report; final CoverageReport report = state.report;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// Header // Header
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -52,16 +53,16 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [UiColors.primary, UiColors.tagInProgress], colors: <Color>[UiColors.primary, UiColors.tagInProgress],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -81,7 +82,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.coverage_report.title, context.t.client_reports.coverage_report.title,
style: const TextStyle( style: const TextStyle(
@@ -113,10 +114,10 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Summary Cards // Summary Cards
Row( Row(
children: [ children: <Widget>[
Expanded( Expanded(
child: _CoverageSummaryCard( child: _CoverageSummaryCard(
label: context.t.client_reports.coverage_report.metrics.avg_coverage, label: context.t.client_reports.coverage_report.metrics.avg_coverage,
@@ -152,7 +153,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
if (report.dailyCoverage.isEmpty) if (report.dailyCoverage.isEmpty)
Center(child: Text(context.t.client_reports.coverage_report.empty_state)) Center(child: Text(context.t.client_reports.coverage_report.empty_state))
else else
...report.dailyCoverage.map((day) => _CoverageListItem( ...report.dailyCoverage.map((CoverageDay day) => _CoverageListItem(
date: DateFormat('EEE, MMM dd').format(day.date), date: DateFormat('EEE, MMM dd').format(day.date),
needed: day.needed, needed: day.needed,
filled: day.filled, filled: day.filled,
@@ -176,10 +177,6 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
} }
class _CoverageSummaryCard extends StatelessWidget { class _CoverageSummaryCard extends StatelessWidget {
final String label;
final String value;
final IconData icon;
final Color color;
const _CoverageSummaryCard({ const _CoverageSummaryCard({
required this.label, required this.label,
@@ -187,6 +184,10 @@ class _CoverageSummaryCard extends StatelessWidget {
required this.icon, required this.icon,
required this.color, required this.color,
}); });
final String label;
final String value;
final IconData icon;
final Color color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -195,7 +196,7 @@ class _CoverageSummaryCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -204,7 +205,7 @@ class _CoverageSummaryCard extends StatelessWidget {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -224,10 +225,6 @@ class _CoverageSummaryCard extends StatelessWidget {
} }
class _CoverageListItem extends StatelessWidget { class _CoverageListItem extends StatelessWidget {
final String date;
final int needed;
final int filled;
final double percentage;
const _CoverageListItem({ const _CoverageListItem({
required this.date, required this.date,
@@ -235,6 +232,10 @@ class _CoverageListItem extends StatelessWidget {
required this.filled, required this.filled,
required this.percentage, required this.percentage,
}); });
final String date;
final int needed;
final int filled;
final double percentage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -255,11 +256,11 @@ class _CoverageListItem extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Row( child: Row(
children: [ children: <Widget>[
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text(date, style: const TextStyle(fontWeight: FontWeight.bold)), Text(date, style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 4), const SizedBox(height: 4),
// Progress Bar // Progress Bar
@@ -278,7 +279,7 @@ class _CoverageListItem extends StatelessWidget {
const SizedBox(width: 16), const SizedBox(width: 16),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: <Widget>[
Text( Text(
'$filled/$needed', '$filled/$needed',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
@@ -298,3 +299,4 @@ class _CoverageListItem extends StatelessWidget {
); );
} }
} }

View File

@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/src/entities/reports/daily_ops_report.dart';
class DailyOpsReportPage extends StatefulWidget { class DailyOpsReportPage extends StatefulWidget {
const DailyOpsReportPage({super.key}); const DailyOpsReportPage({super.key});
@@ -49,12 +50,12 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<DailyOpsBloc>() create: (BuildContext context) => Modular.get<DailyOpsBloc>()
..add(LoadDailyOpsReport(date: _selectedDate)), ..add(LoadDailyOpsReport(date: _selectedDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<DailyOpsBloc, DailyOpsState>( body: BlocBuilder<DailyOpsBloc, DailyOpsState>(
builder: (context, state) { builder: (BuildContext context, DailyOpsState state) {
if (state is DailyOpsLoading) { if (state is DailyOpsLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -64,10 +65,10 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
} }
if (state is DailyOpsLoaded) { if (state is DailyOpsLoaded) {
final report = state.report; final DailyOpsReport report = state.report;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// Header // Header
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -78,7 +79,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: <Color>[
UiColors.primary, UiColors.primary,
UiColors.buttonPrimaryHover UiColors.buttonPrimaryHover
], ],
@@ -88,9 +89,9 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -110,7 +111,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.daily_ops_report context.t.client_reports.daily_ops_report
.title, .title,
@@ -189,7 +190,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Date Selector // Date Selector
GestureDetector( GestureDetector(
onTap: () => _pickDate(context), onTap: () => _pickDate(context),
@@ -198,7 +199,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.06), color: UiColors.black.withOpacity(0.06),
blurRadius: 4, blurRadius: 4,
@@ -208,9 +209,9 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
const Icon( const Icon(
UiIcons.calendar, UiIcons.calendar,
size: 16, size: 16,
@@ -246,7 +247,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
mainAxisSpacing: 12, mainAxisSpacing: 12,
crossAxisSpacing: 12, crossAxisSpacing: 12,
childAspectRatio: 1.2, childAspectRatio: 1.2,
children: [ children: <Widget>[
_OpsStatCard( _OpsStatCard(
label: context.t.client_reports label: context.t.client_reports
.daily_ops_report.metrics.scheduled.label, .daily_ops_report.metrics.scheduled.label,
@@ -339,7 +340,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
), ),
) )
else else
...report.shifts.map((shift) => _ShiftListItem( ...report.shifts.map((DailyOpsShift shift) => _ShiftListItem(
title: shift.title, title: shift.title,
location: shift.location, location: shift.location,
time: time:
@@ -376,11 +377,6 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
} }
class _OpsStatCard extends StatelessWidget { class _OpsStatCard extends StatelessWidget {
final String label;
final String value;
final String subValue;
final Color color;
final IconData icon;
const _OpsStatCard({ const _OpsStatCard({
required this.label, required this.label,
@@ -389,6 +385,11 @@ class _OpsStatCard extends StatelessWidget {
required this.color, required this.color,
required this.icon, required this.icon,
}); });
final String label;
final String value;
final String subValue;
final Color color;
final IconData icon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -397,7 +398,7 @@ class _OpsStatCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.06), color: UiColors.black.withOpacity(0.06),
blurRadius: 4, blurRadius: 4,
@@ -408,9 +409,9 @@ class _OpsStatCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Icon(icon, size: 14, color: color), Icon(icon, size: 14, color: color),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
@@ -429,7 +430,7 @@ class _OpsStatCard extends StatelessWidget {
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
value, value,
style: const TextStyle( style: const TextStyle(
@@ -467,13 +468,6 @@ class _OpsStatCard extends StatelessWidget {
} }
class _ShiftListItem 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({ const _ShiftListItem({
required this.title, required this.title,
@@ -484,6 +478,13 @@ class _ShiftListItem extends StatelessWidget {
required this.status, required this.status,
required this.statusColor, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -493,7 +494,7 @@ class _ShiftListItem extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.02), color: UiColors.black.withOpacity(0.02),
blurRadius: 2, blurRadius: 2,
@@ -501,14 +502,14 @@ class _ShiftListItem extends StatelessWidget {
], ],
), ),
child: Column( child: Column(
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
title, title,
style: const TextStyle( style: const TextStyle(
@@ -519,7 +520,7 @@ class _ShiftListItem extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: <Widget>[
const Icon( const Icon(
UiIcons.mapPin, UiIcons.mapPin,
size: 10, size: 10,
@@ -565,7 +566,7 @@ class _ShiftListItem extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
_infoItem( _infoItem(
context, context,
UiIcons.clock, UiIcons.clock,
@@ -591,12 +592,12 @@ class _ShiftListItem extends StatelessWidget {
Widget _infoItem( Widget _infoItem(
BuildContext context, IconData icon, String label, String value) { BuildContext context, IconData icon, String label, String value) {
return Row( return Row(
children: [ children: <Widget>[
Icon(icon, size: 12, color: UiColors.textSecondary), Icon(icon, size: 12, color: UiColors.textSecondary),
const SizedBox(width: 6), const SizedBox(width: 6),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
label, label,
style: const TextStyle(fontSize: 10, color: UiColors.pinInactive), style: const TextStyle(fontSize: 10, color: UiColors.pinInactive),

View File

@@ -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_bloc.dart';
import 'package:client_reports/src/presentation/blocs/forecast/forecast_event.dart'; import 'package:client_reports/src/presentation/blocs/forecast/forecast_event.dart';
import 'package:client_reports/src/presentation/blocs/forecast/forecast_state.dart'; import 'package:client_reports/src/presentation/blocs/forecast/forecast_state.dart';
@@ -24,12 +25,12 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<ForecastBloc>() create: (BuildContext context) => Modular.get<ForecastBloc>()
..add(LoadForecastReport(startDate: _startDate, endDate: _endDate)), ..add(LoadForecastReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<ForecastBloc, ForecastState>( body: BlocBuilder<ForecastBloc, ForecastState>(
builder: (context, state) { builder: (BuildContext context, ForecastState state) {
if (state is ForecastLoading) { if (state is ForecastLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -39,10 +40,10 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
} }
if (state is ForecastLoaded) { if (state is ForecastLoaded) {
final report = state.report; final ForecastReport report = state.report;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// Header // Header
_buildHeader(context), _buildHeader(context),
@@ -53,7 +54,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Metrics Grid // Metrics Grid
_buildMetricsGrid(context, report), _buildMetricsGrid(context, report),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -82,7 +83,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
) )
else else
...report.weeklyBreakdown.map( ...report.weeklyBreakdown.map(
(week) => _WeeklyBreakdownItem(week: week), (ForecastWeek week) => _WeeklyBreakdownItem(week: week),
), ),
const SizedBox(height: 40), const SizedBox(height: 40),
@@ -112,16 +113,16 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.primary, color: UiColors.primary,
gradient: LinearGradient( gradient: LinearGradient(
colors: [UiColors.primary, Color(0xFF0020A0)], // Deep blue gradient colors: <Color>[UiColors.primary, Color(0xFF0020A0)], // Deep blue gradient
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -141,7 +142,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.forecast_report.title, context.t.client_reports.forecast_report.title,
style: UiTypography.headline3m.copyWith(color: UiColors.white), style: UiTypography.headline3m.copyWith(color: UiColors.white),
@@ -180,7 +181,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
} }
Widget _buildMetricsGrid(BuildContext context, ForecastReport report) { 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( return GridView.count(
crossAxisCount: 2, crossAxisCount: 2,
shrinkWrap: true, shrinkWrap: true,
@@ -188,7 +189,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
mainAxisSpacing: 12, mainAxisSpacing: 12,
crossAxisSpacing: 12, crossAxisSpacing: 12,
childAspectRatio: 1.3, childAspectRatio: 1.3,
children: [ children: <Widget>[
_MetricCard( _MetricCard(
icon: UiIcons.dollar, icon: UiIcons.dollar,
label: t.metrics.four_week_forecast, label: t.metrics.four_week_forecast,
@@ -232,7 +233,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -241,7 +242,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.forecast_report.chart_title, context.t.client_reports.forecast_report.chart_title,
style: UiTypography.headline4m, style: UiTypography.headline4m,
@@ -257,9 +258,9 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// X Axis labels manually if chart doesn't handle them perfectly or for custom look // X Axis labels manually if chart doesn't handle them perfectly or for custom look
Row( const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [ children: <Widget>[
Text('W1', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)), Text('W1', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)),
Text('W1', style: TextStyle(color: UiColors.transparent, fontSize: 12)), // Spacer Text('W1', style: TextStyle(color: UiColors.transparent, fontSize: 12)), // Spacer
Text('W2', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)), Text('W2', style: TextStyle(color: UiColors.textSecondary, fontSize: 12)),
@@ -276,12 +277,6 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
} }
class _MetricCard extends StatelessWidget { class _MetricCard extends StatelessWidget {
final IconData icon;
final String label;
final String value;
final String badgeText;
final Color iconColor;
final Color badgeColor;
const _MetricCard({ const _MetricCard({
required this.icon, required this.icon,
@@ -291,6 +286,12 @@ class _MetricCard extends StatelessWidget {
required this.iconColor, required this.iconColor,
required this.badgeColor, required this.badgeColor,
}); });
final IconData icon;
final String label;
final String value;
final String badgeText;
final Color iconColor;
final Color badgeColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -299,7 +300,7 @@ class _MetricCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 8, blurRadius: 8,
@@ -309,9 +310,9 @@ class _MetricCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Icon(icon, size: 16, color: iconColor), Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
@@ -349,13 +350,13 @@ class _MetricCard extends StatelessWidget {
} }
class _WeeklyBreakdownItem extends StatelessWidget { class _WeeklyBreakdownItem extends StatelessWidget {
final ForecastWeek week;
const _WeeklyBreakdownItem({required this.week}); const _WeeklyBreakdownItem({required this.week});
final ForecastWeek week;
@override @override
Widget build(BuildContext context) { 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( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -365,10 +366,10 @@ class _WeeklyBreakdownItem extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Column( child: Column(
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Text( Text(
t.week(index: week.weekNumber), t.week(index: week.weekNumber),
style: UiTypography.headline4m, style: UiTypography.headline4m,
@@ -391,7 +392,7 @@ class _WeeklyBreakdownItem extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
_buildStat(t.shifts, week.shiftsCount.toString()), _buildStat(t.shifts, week.shiftsCount.toString()),
_buildStat(t.hours, week.hoursCount.toStringAsFixed(0)), _buildStat(t.hours, week.hoursCount.toStringAsFixed(0)),
_buildStat(t.avg_shift, NumberFormat.currency(symbol: r'$', decimalDigits: 0).format(week.avgCostPerShift)), _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) { Widget _buildStat(String label, String value) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text(label, style: UiTypography.footnote1r.textSecondary), Text(label, style: UiTypography.footnote1r.textSecondary),
const SizedBox(height: 4), const SizedBox(height: 4),
Text(value, style: UiTypography.body1m), Text(value, style: UiTypography.body1m),
@@ -415,9 +416,9 @@ class _WeeklyBreakdownItem extends StatelessWidget {
} }
class _ForecastChart extends StatelessWidget { class _ForecastChart extends StatelessWidget {
final List<ForecastPoint> points;
const _ForecastChart({required this.points}); const _ForecastChart({required this.points});
final List<ForecastPoint> points;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -430,11 +431,11 @@ class _ForecastChart extends StatelessWidget {
show: true, show: true,
drawVerticalLine: false, drawVerticalLine: false,
horizontalInterval: 5000, // Dynamic? horizontalInterval: 5000, // Dynamic?
getDrawingHorizontalLine: (value) { getDrawingHorizontalLine: (double value) {
return FlLine( return const FlLine(
color: UiColors.borderInactive, color: UiColors.borderInactive,
strokeWidth: 1, strokeWidth: 1,
dashArray: [5, 5], dashArray: <int>[5, 5],
); );
}, },
), ),
@@ -443,9 +444,9 @@ class _ForecastChart extends StatelessWidget {
minX: 0, minX: 0,
maxX: points.length.toDouble() - 1, maxX: points.length.toDouble() - 1,
// minY: 0, // Let it scale automatically // minY: 0, // Let it scale automatically
lineBarsData: [ lineBarsData: <LineChartBarData>[
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); return FlSpot(e.key.toDouble(), e.value.projectedCost);
}).toList(), }).toList(),
isCurved: true, isCurved: true,
@@ -454,7 +455,7 @@ class _ForecastChart extends StatelessWidget {
isStrokeCapRound: true, isStrokeCapRound: true,
dotData: FlDotData( dotData: FlDotData(
show: true, show: true,
getDotPainter: (spot, percent, barData, index) { getDotPainter: (FlSpot spot, double percent, LineChartBarData barData, int index) {
return FlDotCirclePainter( return FlDotCirclePainter(
radius: 4, radius: 4,
color: UiColors.textWarning, color: UiColors.textWarning,
@@ -473,3 +474,4 @@ class _ForecastChart extends StatelessWidget {
); );
} }
} }

View File

@@ -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: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_bloc.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_event.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> { class _NoShowReportPageState extends State<NoShowReportPage> {
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30)); final DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
DateTime _endDate = DateTime.now(); final DateTime _endDate = DateTime.now();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<NoShowBloc>() create: (BuildContext context) => Modular.get<NoShowBloc>()
..add(LoadNoShowReport(startDate: _startDate, endDate: _endDate)), ..add(LoadNoShowReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<NoShowBloc, NoShowState>( body: BlocBuilder<NoShowBloc, NoShowState>(
builder: (context, state) { builder: (BuildContext context, NoShowState state) {
if (state is NoShowLoading) { if (state is NoShowLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -38,12 +39,12 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
} }
if (state is NoShowLoaded) { if (state is NoShowLoaded) {
final report = state.report; final NoShowReport report = state.report;
final uniqueWorkers = report.flaggedWorkers.length; final int uniqueWorkers = report.flaggedWorkers.length;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// ── Header ────────────────────────────────────────── // ── Header ──────────────────────────────────────────
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 60, top: 60,
@@ -53,7 +54,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: <Color>[
UiColors.primary, UiColors.primary,
UiColors.buttonPrimaryHover, UiColors.buttonPrimaryHover,
], ],
@@ -63,9 +64,9 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -85,7 +86,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.no_show_report.title, context.t.client_reports.no_show_report.title,
style: const TextStyle( style: const TextStyle(
@@ -150,17 +151,17 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
), ),
), ),
// ── Content ───────────────────────────────────────── // ── Content ─────────────────────────────────────────
Transform.translate( Transform.translate(
offset: const Offset(0, -16), offset: const Offset(0, -16),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// 3-chip summary row (matches prototype) // 3-chip summary row (matches prototype)
Row( Row(
children: [ children: <Widget>[
Expanded( Expanded(
child: _SummaryChip( child: _SummaryChip(
icon: UiIcons.warning, icon: UiIcons.warning,
@@ -220,7 +221,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
) )
else else
...report.flaggedWorkers.map( ...report.flaggedWorkers.map(
(worker) => _WorkerCard(worker: worker), (NoShowWorker worker) => _WorkerCard(worker: worker),
), ),
const SizedBox(height: 40), 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 { class _SummaryChip extends StatelessWidget {
final IconData icon;
final Color iconColor;
final String label;
final String value;
const _SummaryChip({ const _SummaryChip({
required this.icon, required this.icon,
@@ -253,6 +250,10 @@ class _SummaryChip extends StatelessWidget {
required this.label, required this.label,
required this.value, required this.value,
}); });
final IconData icon;
final Color iconColor;
final String label;
final String value;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -261,7 +262,7 @@ class _SummaryChip extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.06), color: UiColors.black.withOpacity(0.06),
blurRadius: 8, blurRadius: 8,
@@ -271,9 +272,9 @@ class _SummaryChip extends StatelessWidget {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Icon(icon, size: 12, color: iconColor), Icon(icon, size: 12, color: iconColor),
const SizedBox(width: 4), const SizedBox(width: 4),
Expanded( 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 { class _WorkerCard extends StatelessWidget {
final NoShowWorker worker;
const _WorkerCard({required this.worker}); const _WorkerCard({required this.worker});
final NoShowWorker worker;
String _riskLabel(BuildContext context, int count) { String _riskLabel(BuildContext context, int count) {
if (count >= 3) return context.t.client_reports.no_show_report.risks.high; if (count >= 3) return context.t.client_reports.no_show_report.risks.high;
@@ -330,9 +331,9 @@ class _WorkerCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final riskLabel = _riskLabel(context, worker.noShowCount); final String riskLabel = _riskLabel(context, worker.noShowCount);
final riskColor = _riskColor(worker.noShowCount); final Color riskColor = _riskColor(worker.noShowCount);
final riskBg = _riskBg(worker.noShowCount); final Color riskBg = _riskBg(worker.noShowCount);
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: 12),
@@ -340,7 +341,7 @@ class _WorkerCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 6, blurRadius: 6,
@@ -348,12 +349,12 @@ class _WorkerCard extends StatelessWidget {
], ],
), ),
child: Column( child: Column(
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Container( Container(
width: 40, width: 40,
height: 40, height: 40,
@@ -370,7 +371,7 @@ class _WorkerCard extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
worker.fullName, worker.fullName,
style: const TextStyle( style: const TextStyle(
@@ -416,7 +417,7 @@ class _WorkerCard extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.no_show_report.latest_incident, context.t.client_reports.no_show_report.latest_incident,
style: const TextStyle( style: const TextStyle(
@@ -447,4 +448,5 @@ class _WorkerCard extends StatelessWidget {
} }
} }
// ── Insight line ───────────────────────────────────────────────────────────── // ── Insight line ─────────────────────────────────────────────────────────────

View File

@@ -6,6 +6,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/src/entities/reports/performance_report.dart';
class PerformanceReportPage extends StatefulWidget { class PerformanceReportPage extends StatefulWidget {
const PerformanceReportPage({super.key}); const PerformanceReportPage({super.key});
@@ -15,18 +16,18 @@ class PerformanceReportPage extends StatefulWidget {
} }
class _PerformanceReportPageState extends State<PerformanceReportPage> { class _PerformanceReportPageState extends State<PerformanceReportPage> {
DateTime _startDate = DateTime.now().subtract(const Duration(days: 30)); final DateTime _startDate = DateTime.now().subtract(const Duration(days: 30));
DateTime _endDate = DateTime.now(); final DateTime _endDate = DateTime.now();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<PerformanceBloc>() create: (BuildContext context) => Modular.get<PerformanceBloc>()
..add(LoadPerformanceReport(startDate: _startDate, endDate: _endDate)), ..add(LoadPerformanceReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<PerformanceBloc, PerformanceState>( body: BlocBuilder<PerformanceBloc, PerformanceState>(
builder: (context, state) { builder: (BuildContext context, PerformanceState state) {
if (state is PerformanceLoading) { if (state is PerformanceLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -36,10 +37,10 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
} }
if (state is PerformanceLoaded) { if (state is PerformanceLoaded) {
final report = state.report; final PerformanceReport report = state.report;
// Compute overall score (0100) from the 4 KPIs // Compute overall score (0100) from the 4 KPIs
final overallScore = ((report.fillRate * 0.3) + final double overallScore = ((report.fillRate * 0.3) +
(report.completionRate * 0.3) + (report.completionRate * 0.3) +
(report.onTimeRate * 0.25) + (report.onTimeRate * 0.25) +
// avg fill time: 3h target → invert to score // avg fill time: 3h target → invert to score
@@ -49,24 +50,24 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
0.15)) 0.15))
.clamp(0.0, 100.0); .clamp(0.0, 100.0);
final scoreLabel = overallScore >= 90 final String scoreLabel = overallScore >= 90
? context.t.client_reports.performance_report.overall_score.excellent ? context.t.client_reports.performance_report.overall_score.excellent
: overallScore >= 75 : overallScore >= 75
? context.t.client_reports.performance_report.overall_score.good ? context.t.client_reports.performance_report.overall_score.good
: context.t.client_reports.performance_report.overall_score.needs_work; : context.t.client_reports.performance_report.overall_score.needs_work;
final scoreLabelColor = overallScore >= 90 final Color scoreLabelColor = overallScore >= 90
? UiColors.success ? UiColors.success
: overallScore >= 75 : overallScore >= 75
? UiColors.textWarning ? UiColors.textWarning
: UiColors.error; : UiColors.error;
final scoreLabelBg = overallScore >= 90 final Color scoreLabelBg = overallScore >= 90
? UiColors.tagSuccess ? UiColors.tagSuccess
: overallScore >= 75 : overallScore >= 75
? UiColors.tagPending ? UiColors.tagPending
: UiColors.tagError; : UiColors.tagError;
// KPI rows: label, value, target, color, met status // KPI rows: label, value, target, color, met status
final kpis = [ final List<_KpiData> kpis = <_KpiData>[
_KpiData( _KpiData(
icon: UiIcons.users, icon: UiIcons.users,
iconColor: UiColors.primary, iconColor: UiColors.primary,
@@ -119,7 +120,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// ── Header ─────────────────────────────────────────── // ── Header ───────────────────────────────────────────
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -130,16 +131,16 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [UiColors.primary, UiColors.buttonPrimaryHover], colors: <Color>[UiColors.primary, UiColors.buttonPrimaryHover],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -159,7 +160,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.performance_report context.t.client_reports.performance_report
.title, .title,
@@ -229,7 +230,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
children: [ children: <Widget>[
// ── Overall Score Hero Card ─────────────────── // ── Overall Score Hero Card ───────────────────
Container( Container(
width: double.infinity, width: double.infinity,
@@ -240,7 +241,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF0F4FF), color: const Color(0xFFF0F4FF),
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -249,7 +250,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
], ],
), ),
child: Column( child: Column(
children: [ children: <Widget>[
const Icon( const Icon(
UiIcons.chart, UiIcons.chart,
size: 32, size: 32,
@@ -258,7 +259,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
context.t.client_reports.performance_report.overall_score.title, context.t.client_reports.performance_report.overall_score.title,
style: TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 13,
color: UiColors.textSecondary, color: UiColors.textSecondary,
), ),
@@ -303,7 +304,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -312,7 +313,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.performance_report.kpis_title, context.t.client_reports.performance_report.kpis_title,
style: const TextStyle( style: const TextStyle(
@@ -324,7 +325,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
...kpis.map( ...kpis.map(
(kpi) => _KpiRow(kpi: kpi), (_KpiData kpi) => _KpiRow(kpi: kpi),
), ),
], ],
), ),
@@ -349,15 +350,6 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
// ── KPI data model ──────────────────────────────────────────────────────────── // ── KPI data model ────────────────────────────────────────────────────────────
class _KpiData { class _KpiData {
final IconData icon;
final Color iconColor;
final String label;
final String target;
final double value; // 0100 for bar
final String displayValue;
final Color barColor;
final bool met;
final bool close;
const _KpiData({ const _KpiData({
required this.icon, required this.icon,
@@ -370,27 +362,36 @@ class _KpiData {
required this.met, required this.met,
required this.close, required this.close,
}); });
final IconData icon;
final Color iconColor;
final String label;
final String target;
final double value; // 0100 for bar
final String displayValue;
final Color barColor;
final bool met;
final bool close;
} }
// ── KPI row widget ──────────────────────────────────────────────────────────── // ── KPI row widget ────────────────────────────────────────────────────────────
class _KpiRow extends StatelessWidget { class _KpiRow extends StatelessWidget {
final _KpiData kpi;
const _KpiRow({required this.kpi}); const _KpiRow({required this.kpi});
final _KpiData kpi;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final badgeText = kpi.met final String badgeText = kpi.met
? context.t.client_reports.performance_report.kpis.met ? context.t.client_reports.performance_report.kpis.met
: kpi.close : kpi.close
? context.t.client_reports.performance_report.kpis.close ? context.t.client_reports.performance_report.kpis.close
: context.t.client_reports.performance_report.kpis.miss; : context.t.client_reports.performance_report.kpis.miss;
final badgeColor = kpi.met final Color badgeColor = kpi.met
? UiColors.success ? UiColors.success
: kpi.close : kpi.close
? UiColors.textWarning ? UiColors.textWarning
: UiColors.error; : UiColors.error;
final badgeBg = kpi.met final Color badgeBg = kpi.met
? UiColors.tagSuccess ? UiColors.tagSuccess
: kpi.close : kpi.close
? UiColors.tagPending ? UiColors.tagPending
@@ -399,9 +400,9 @@ class _KpiRow extends StatelessWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.only(bottom: 20),
child: Column( child: Column(
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Container( Container(
width: 36, width: 36,
height: 36, height: 36,
@@ -415,7 +416,7 @@ class _KpiRow extends StatelessWidget {
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
kpi.label, kpi.label,
style: const TextStyle( style: const TextStyle(
@@ -437,7 +438,7 @@ class _KpiRow extends StatelessWidget {
// Value + badge inline (matches prototype) // Value + badge inline (matches prototype)
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: <Widget>[
Text( Text(
kpi.displayValue, kpi.displayValue,
style: const TextStyle( style: const TextStyle(

View File

@@ -24,7 +24,7 @@ class _ReportsPageState extends State<ReportsPage>
late ReportsSummaryBloc _summaryBloc; late ReportsSummaryBloc _summaryBloc;
// Date ranges per tab: Today, Week, Month, Quarter // 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),
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) { void _loadSummary(int tabIndex) {
final range = _dateRanges[tabIndex]; final (DateTime, DateTime) range = _dateRanges[tabIndex];
_summaryBloc.add(LoadReportsSummary( _summaryBloc.add(LoadReportsSummary(
startDate: range.$1, startDate: range.$1,
endDate: range.$2, endDate: range.$2,
@@ -85,7 +85,7 @@ class _ReportsPageState extends State<ReportsPage>
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// Header with title and tabs // Header with title and tabs
ReportsHeader( ReportsHeader(
tabController: _tabController, tabController: _tabController,
@@ -93,20 +93,20 @@ class _ReportsPageState extends State<ReportsPage>
), ),
// Content // Content
Padding( const Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Key Metrics Grid // Key Metrics Grid
const MetricsGrid(), MetricsGrid(),
const SizedBox(height: 24), SizedBox(height: 24),
// Quick Reports Section // Quick Reports Section
const QuickReportsSection(), QuickReportsSection(),
const SizedBox(height: 40), SizedBox(height: 40),
], ],
), ),
), ),

View File

@@ -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_bloc.dart';
import 'package:client_reports/src/presentation/blocs/spend/spend_event.dart'; import 'package:client_reports/src/presentation/blocs/spend/spend_event.dart';
import 'package:client_reports/src/presentation/blocs/spend/spend_state.dart'; import 'package:client_reports/src/presentation/blocs/spend/spend_state.dart';
@@ -24,10 +25,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final now = DateTime.now(); final DateTime now = DateTime.now();
// Monday alignment logic // Monday alignment logic
final diff = now.weekday - DateTime.monday; final int diff = now.weekday - DateTime.monday;
final monday = now.subtract(Duration(days: diff)); final DateTime monday = now.subtract(Duration(days: diff));
_startDate = DateTime(monday.year, monday.month, monday.day); _startDate = DateTime(monday.year, monday.month, monday.day);
_endDate = _startDate.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59)); _endDate = _startDate.add(const Duration(days: 6, hours: 23, minutes: 59, seconds: 59));
} }
@@ -35,12 +36,12 @@ class _SpendReportPageState extends State<SpendReportPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => Modular.get<SpendBloc>() create: (BuildContext context) => Modular.get<SpendBloc>()
..add(LoadSpendReport(startDate: _startDate, endDate: _endDate)), ..add(LoadSpendReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold( child: Scaffold(
backgroundColor: UiColors.bgMenu, backgroundColor: UiColors.bgMenu,
body: BlocBuilder<SpendBloc, SpendState>( body: BlocBuilder<SpendBloc, SpendState>(
builder: (context, state) { builder: (BuildContext context, SpendState state) {
if (state is SpendLoading) { if (state is SpendLoading) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@@ -50,10 +51,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
} }
if (state is SpendLoaded) { if (state is SpendLoaded) {
final report = state.report; final SpendReport report = state.report;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
children: [ children: <Widget>[
// Header // Header
Container( Container(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@@ -67,9 +68,9 @@ class _SpendReportPageState extends State<SpendReportPage> {
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Navigator.of(context).pop(), onTap: () => Navigator.of(context).pop(),
child: Container( child: Container(
@@ -89,7 +90,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.spend_report.title, context.t.client_reports.spend_report.title,
style: const TextStyle( style: const TextStyle(
@@ -167,10 +168,10 @@ class _SpendReportPageState extends State<SpendReportPage> {
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Summary Cards (New Style) // Summary Cards (New Style)
Row( Row(
children: [ children: <Widget>[
Expanded( Expanded(
child: _SpendStatCard( child: _SpendStatCard(
label: context.t.client_reports.spend_report label: context.t.client_reports.spend_report
@@ -209,7 +210,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -219,7 +220,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.spend_report.chart_title, context.t.client_reports.spend_report.chart_title,
style: const TextStyle( style: const TextStyle(
@@ -262,9 +263,9 @@ class _SpendReportPageState extends State<SpendReportPage> {
} }
class _SpendBarChart extends StatelessWidget { class _SpendBarChart extends StatelessWidget {
final List<dynamic> chartData;
const _SpendBarChart({required this.chartData}); const _SpendBarChart({required this.chartData});
final List<dynamic> chartData;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -272,14 +273,14 @@ class _SpendBarChart extends StatelessWidget {
BarChartData( BarChartData(
alignment: BarChartAlignment.spaceAround, alignment: BarChartAlignment.spaceAround,
maxY: (chartData.fold<double>(0, maxY: (chartData.fold<double>(0,
(prev, element) => (double prev, element) =>
element.amount > prev ? element.amount : prev) * element.amount > prev ? element.amount : prev) *
1.2) 1.2)
.ceilToDouble(), .ceilToDouble(),
barTouchData: BarTouchData( barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData( touchTooltipData: BarTouchTooltipData(
tooltipPadding: const EdgeInsets.all(8), tooltipPadding: const EdgeInsets.all(8),
getTooltipItem: (group, groupIndex, rod, rodIndex) { getTooltipItem: (BarChartGroupData group, int groupIndex, BarChartRodData rod, int rodIndex) {
return BarTooltipItem( return BarTooltipItem(
'\$${rod.toY.round()}', '\$${rod.toY.round()}',
const TextStyle( const TextStyle(
@@ -296,7 +297,7 @@ class _SpendBarChart extends StatelessWidget {
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
reservedSize: 30, reservedSize: 30,
getTitlesWidget: (value, meta) { getTitlesWidget: (double value, TitleMeta meta) {
if (value.toInt() >= chartData.length) return const SizedBox(); if (value.toInt() >= chartData.length) return const SizedBox();
final date = chartData[value.toInt()].date; final date = chartData[value.toInt()].date;
return SideTitleWidget( return SideTitleWidget(
@@ -317,7 +318,7 @@ class _SpendBarChart extends StatelessWidget {
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
reservedSize: 40, reservedSize: 40,
getTitlesWidget: (value, meta) { getTitlesWidget: (double value, TitleMeta meta) {
if (value == 0) return const SizedBox(); if (value == 0) return const SizedBox();
return SideTitleWidget( return SideTitleWidget(
axisSide: meta.axisSide, axisSide: meta.axisSide,
@@ -343,7 +344,7 @@ class _SpendBarChart extends StatelessWidget {
show: true, show: true,
drawVerticalLine: false, drawVerticalLine: false,
horizontalInterval: 1000, horizontalInterval: 1000,
getDrawingHorizontalLine: (value) => FlLine( getDrawingHorizontalLine: (double value) => const FlLine(
color: UiColors.bgSecondary, color: UiColors.bgSecondary,
strokeWidth: 1, strokeWidth: 1,
), ),
@@ -351,9 +352,9 @@ class _SpendBarChart extends StatelessWidget {
borderData: FlBorderData(show: false), borderData: FlBorderData(show: false),
barGroups: List.generate( barGroups: List.generate(
chartData.length, chartData.length,
(index) => BarChartGroupData( (int index) => BarChartGroupData(
x: index, x: index,
barRods: [ barRods: <BarChartRodData>[
BarChartRodData( BarChartRodData(
toY: chartData[index].amount, toY: chartData[index].amount,
color: UiColors.success, color: UiColors.success,
@@ -371,11 +372,6 @@ class _SpendBarChart extends StatelessWidget {
} }
class _SpendStatCard extends StatelessWidget { class _SpendStatCard extends StatelessWidget {
final String label;
final String value;
final String pillText;
final Color themeColor;
final IconData icon;
const _SpendStatCard({ const _SpendStatCard({
required this.label, required this.label,
@@ -384,6 +380,11 @@ class _SpendStatCard extends StatelessWidget {
required this.themeColor, required this.themeColor,
required this.icon, required this.icon,
}); });
final String label;
final String value;
final String pillText;
final Color themeColor;
final IconData icon;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -392,7 +393,7 @@ class _SpendStatCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.06), color: UiColors.black.withOpacity(0.06),
blurRadius: 8, blurRadius: 8,
@@ -402,9 +403,9 @@ class _SpendStatCard extends StatelessWidget {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Row( Row(
children: [ children: <Widget>[
Icon(icon, size: 14, color: themeColor), Icon(icon, size: 14, color: themeColor),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
@@ -453,9 +454,9 @@ class _SpendStatCard extends StatelessWidget {
} }
class _SpendByIndustryCard extends StatelessWidget { class _SpendByIndustryCard extends StatelessWidget {
final List<SpendIndustryCategory> industries;
const _SpendByIndustryCard({required this.industries}); const _SpendByIndustryCard({required this.industries});
final List<SpendIndustryCategory> industries;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -464,7 +465,7 @@ class _SpendByIndustryCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.04), color: UiColors.black.withOpacity(0.04),
blurRadius: 10, blurRadius: 10,
@@ -474,7 +475,7 @@ class _SpendByIndustryCard extends StatelessWidget {
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
context.t.client_reports.spend_report.spend_by_industry, context.t.client_reports.spend_report.spend_by_industry,
style: const TextStyle( style: const TextStyle(
@@ -495,14 +496,14 @@ class _SpendByIndustryCard extends StatelessWidget {
), ),
) )
else else
...industries.map((ind) => Padding( ...industries.map((SpendIndustryCategory ind) => Padding(
padding: const EdgeInsets.only(bottom: 24.0), padding: const EdgeInsets.only(bottom: 24.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
Text( Text(
ind.name, ind.name,
style: const TextStyle( style: const TextStyle(
@@ -547,3 +548,4 @@ class _SpendByIndustryCard extends StatelessWidget {
); );
} }
} }

View File

@@ -6,6 +6,17 @@ import 'package:flutter/material.dart';
/// Shows a metric with an icon, label, value, and a badge with contextual /// Shows a metric with an icon, label, value, and a badge with contextual
/// information. Used in the metrics grid of the reports page. /// information. Used in the metrics grid of the reports page.
class MetricCard extends StatelessWidget { 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. /// The icon to display for this metric.
final IconData icon; final IconData icon;
@@ -27,17 +38,6 @@ class MetricCard extends StatelessWidget {
/// Color for the icon. /// Color for the icon.
final Color iconColor; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
@@ -45,7 +45,7 @@ class MetricCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.06), color: UiColors.black.withOpacity(0.06),
blurRadius: 4, blurRadius: 4,
@@ -56,10 +56,10 @@ class MetricCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
// Icon and Label // Icon and Label
Row( Row(
children: [ children: <Widget>[
Icon(icon, size: 16, color: iconColor), Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
@@ -78,7 +78,7 @@ class MetricCard extends StatelessWidget {
// Value and Badge // Value and Badge
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
value, value,
style: const TextStyle( style: const TextStyle(

View File

@@ -5,6 +5,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/src/entities/reports/reports_summary.dart';
import 'metric_card.dart'; import 'metric_card.dart';
@@ -25,7 +26,7 @@ class MetricsGrid extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ReportsSummaryBloc, ReportsSummaryState>( return BlocBuilder<ReportsSummaryBloc, ReportsSummaryState>(
builder: (context, state) { builder: (BuildContext context, ReportsSummaryState state) {
// Loading or Initial State // Loading or Initial State
if (state is ReportsSummaryLoading || state is ReportsSummaryInitial) { if (state is ReportsSummaryLoading || state is ReportsSummaryInitial) {
return const Padding( return const Padding(
@@ -45,7 +46,7 @@ class MetricsGrid extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Row( child: Row(
children: [ children: <Widget>[
const Icon(UiIcons.warning, const Icon(UiIcons.warning,
color: UiColors.error, size: 16), color: UiColors.error, size: 16),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -63,8 +64,8 @@ class MetricsGrid extends StatelessWidget {
} }
// Loaded State // Loaded State
final summary = (state as ReportsSummaryLoaded).summary; final ReportsSummary summary = (state as ReportsSummaryLoaded).summary;
final currencyFmt = NumberFormat.currency( final NumberFormat currencyFmt = NumberFormat.currency(
symbol: '\$', decimalDigits: 0); symbol: '\$', decimalDigits: 0);
return GridView.count( return GridView.count(
@@ -74,7 +75,7 @@ class MetricsGrid extends StatelessWidget {
mainAxisSpacing: 12, mainAxisSpacing: 12,
crossAxisSpacing: 12, crossAxisSpacing: 12,
childAspectRatio: 1.2, childAspectRatio: 1.2,
children: [ children: <Widget>[
// Total Hours // Total Hours
MetricCard( MetricCard(
icon: UiIcons.clock, icon: UiIcons.clock,

View File

@@ -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:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -18,7 +19,7 @@ class QuickReportsSection extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
// Title // Title
Text( Text(
context.t.client_reports.quick_reports.title, context.t.client_reports.quick_reports.title,
@@ -33,7 +34,7 @@ class QuickReportsSection extends StatelessWidget {
mainAxisSpacing: 12, mainAxisSpacing: 12,
crossAxisSpacing: 12, crossAxisSpacing: 12,
childAspectRatio: 1.3, childAspectRatio: 1.3,
children: [ children: <Widget>[
// Daily Operations // Daily Operations
ReportCard( ReportCard(
icon: UiIcons.calendar, icon: UiIcons.calendar,
@@ -89,3 +90,4 @@ class QuickReportsSection extends StatelessWidget {
); );
} }
} }

View File

@@ -8,6 +8,15 @@ import 'package:flutter_modular/flutter_modular.dart';
/// Displays an icon, name, and a quick navigation to a report page. /// Displays an icon, name, and a quick navigation to a report page.
/// Used in the quick reports grid of the reports page. /// Used in the quick reports grid of the reports page.
class ReportCard extends StatelessWidget { 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. /// The icon to display for this report.
final IconData icon; final IconData icon;
@@ -23,15 +32,6 @@ class ReportCard extends StatelessWidget {
/// Navigation route to the report page. /// Navigation route to the report page.
final String route; final String route;
const ReportCard({
super.key,
required this.icon,
required this.name,
required this.iconBgColor,
required this.iconColor,
required this.route,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
@@ -41,7 +41,7 @@ class ReportCard extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.02), color: UiColors.black.withOpacity(0.02),
blurRadius: 2, blurRadius: 2,
@@ -51,7 +51,7 @@ class ReportCard extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: <Widget>[
// Icon Container // Icon Container
Container( Container(
width: 40, width: 40,
@@ -65,7 +65,7 @@ class ReportCard extends StatelessWidget {
// Name and Export Info // Name and Export Info
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
Text( Text(
name, name,
style: const TextStyle( style: const TextStyle(
@@ -78,7 +78,7 @@ class ReportCard extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Row( Row(
children: [ children: <Widget>[
const Icon( const Icon(
UiIcons.download, UiIcons.download,
size: 12, size: 12,

View File

@@ -32,7 +32,7 @@ class ReportsHeader extends StatelessWidget {
), ),
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: <Color>[
UiColors.primary, UiColors.primary,
UiColors.buttonPrimaryHover, UiColors.buttonPrimaryHover,
], ],
@@ -41,10 +41,10 @@ class ReportsHeader extends StatelessWidget {
), ),
), ),
child: Column( child: Column(
children: [ children: <Widget>[
// Title and Back Button // Title and Back Button
Row( Row(
children: [ children: <Widget>[
GestureDetector( GestureDetector(
onTap: () => Modular.to.toClientHome(), onTap: () => Modular.to.toClientHome(),
child: Container( child: Container(
@@ -104,7 +104,7 @@ class ReportsHeader extends StatelessWidget {
), ),
indicatorSize: TabBarIndicatorSize.tab, indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
tabs: [ tabs: <Widget>[
Tab(text: context.t.client_reports.tabs.today), Tab(text: context.t.client_reports.tabs.today),
Tab(text: context.t.client_reports.tabs.week), Tab(text: context.t.client_reports.tabs.week),
Tab(text: context.t.client_reports.tabs.month), Tab(text: context.t.client_reports.tabs.month),

View File

@@ -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/data/repositories_impl/reports_repository_impl.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart'; import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.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 { class ReportsModule extends Module {
@override @override
List<Module> get imports => [DataConnectModule()]; List<Module> get imports => <Module>[DataConnectModule()];
@override @override
void binds(Injector i) { void binds(Injector i) {
@@ -44,3 +45,4 @@ class ReportsModule extends Module {
r.child('/no-show', child: (_) => const NoShowReportPage()); r.child('/no-show', child: (_) => const NoShowReportPage());
} }
} }

Some files were not shown because too many files have changed in this diff Show More