From 0622002014879e2524ac62a608710c58e2d947f6 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sun, 25 Jan 2026 21:16:50 -0500 Subject: [PATCH] feat: add staff time card feature with localization and repository implementation - Implemented Spanish translations for time card related strings. - Created TimeCardRepository to fetch time card data based on shifts. - Added use cases for retrieving time cards and payment history. - Developed BLoC for managing time card state and events. - Designed UI components for displaying time card summary and shift history. - Integrated time card navigation within the staff main module. - Updated pubspec.yaml files to include new dependencies and modules. --- .../lib/src/l10n/en.i18n.json | 15 ++ .../lib/src/l10n/es.i18n.json | 15 ++ .../lib/src/l10n/strings.g.dart | 4 +- .../lib/src/l10n/strings_en.g.dart | 67 +++++++ .../lib/src/l10n/strings_es.g.dart | 44 +++++ .../payments_repository_impl.dart | 7 + .../get_payment_history_arguments.dart | 12 ++ .../src/domain/entities/payment_summary.dart | 23 --- .../domain/entities/payment_transaction.dart | 41 ----- .../repositories/payments_repository.dart | 6 +- .../usecases/get_payment_history_usecase.dart | 9 +- .../usecases/get_payment_summary_usecase.dart | 5 + .../blocs/payments/payments_bloc.dart | 11 +- .../navigation/profile_navigator.dart | 2 +- .../time_card_repository_impl.dart | 64 +++++++ .../arguments/get_time_cards_arguments.dart | 12 ++ .../lib/src/domain/entities/time_card.dart | 56 ++++++ .../repositories/time_card_repository.dart | 12 ++ .../usecases/get_time_cards_usecase.dart | 19 ++ .../presentation/blocs/time_card_bloc.dart | 42 +++++ .../presentation/blocs/time_card_event.dart | 23 +++ .../presentation/blocs/time_card_state.dart | 32 ++++ .../presentation/pages/time_card_page.dart | 92 ++++++++++ .../presentation/widgets/month_selector.dart | 51 ++++++ .../widgets/shift_history_list.dart | 46 +++++ .../widgets/time_card_summary.dart | 84 +++++++++ .../presentation/widgets/timesheet_card.dart | 171 ++++++++++++++++++ .../lib/src/staff_time_card_module.dart | 35 ++++ .../time_card/lib/staff_time_card.dart | 1 + .../finances/time_card/pubspec.yaml | 31 ++++ .../staff_main/lib/src/staff_main_module.dart | 5 + .../features/staff/staff_main/pubspec.yaml | 2 + apps/mobile/pubspec.lock | 7 + 33 files changed, 972 insertions(+), 74 deletions(-) create mode 100644 apps/mobile/packages/features/staff/payments/lib/src/domain/arguments/get_payment_history_arguments.dart delete mode 100644 apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_summary.dart delete mode 100644 apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_transaction.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/arguments/get_time_cards_arguments.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/repositories/time_card_repository.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/usecases/get_time_cards_usecase.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_bloc.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_event.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_state.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/month_selector.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/shift_history_list.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/time_card_summary.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/staff_time_card.dart create mode 100644 apps/mobile/packages/features/staff/profile_sections/finances/time_card/pubspec.yaml diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index 514f70cb..00aa820f 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -704,6 +704,21 @@ "immediate_start": "Immediate start", "no_experience": "No experience" } + }, + "staff_time_card": { + "title": "Timecard", + "hours_worked": "Hours Worked", + "total_earnings": "Total Earnings", + "shift_history": "Shift History", + "no_shifts": "No shifts for this month", + "hours": "hours", + "per_hr": "/hr", + "status": { + "approved": "Approved", + "disputed": "Disputed", + "paid": "Paid", + "pending": "Pending" + } } } diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json index 21177038..4cffd42e 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json @@ -703,5 +703,20 @@ "immediate_start": "Immediate start", "no_experience": "No experience" } + }, + "staff_time_card": { + "title": "Tarjeta de tiempo", + "hours_worked": "Horas trabajadas", + "total_earnings": "Ganancias totales", + "shift_history": "Historial de turnos", + "no_shifts": "No hay turnos para este mes", + "hours": "horas", + "per_hr": "/hr", + "status": { + "approved": "Aprobado", + "disputed": "Disputado", + "paid": "Pagado", + "pending": "Pendiente" + } } } diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/strings.g.dart b/apps/mobile/packages/core_localization/lib/src/l10n/strings.g.dart index e18f4423..ed16a6a0 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/strings.g.dart +++ b/apps/mobile/packages/core_localization/lib/src/l10n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 1004 (502 per locale) +/// Strings: 1026 (513 per locale) /// -/// Built on 2026-01-25 at 22:04 UTC +/// Built on 2026-01-26 at 02:14 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/strings_en.g.dart b/apps/mobile/packages/core_localization/lib/src/l10n/strings_en.g.dart index 40d8a78b..70903846 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/strings_en.g.dart +++ b/apps/mobile/packages/core_localization/lib/src/l10n/strings_en.g.dart @@ -56,6 +56,7 @@ class Translations with BaseTranslations { late final TranslationsStaffCertificatesEn staff_certificates = TranslationsStaffCertificatesEn._(_root); late final TranslationsStaffProfileAttireEn staff_profile_attire = TranslationsStaffProfileAttireEn._(_root); late final TranslationsStaffShiftsEn staff_shifts = TranslationsStaffShiftsEn._(_root); + late final TranslationsStaffTimeCardEn staff_time_card = TranslationsStaffTimeCardEn._(_root); } // Path: common @@ -387,6 +388,38 @@ class TranslationsStaffShiftsEn { late final TranslationsStaffShiftsTagsEn tags = TranslationsStaffShiftsTagsEn._(_root); } +// Path: staff_time_card +class TranslationsStaffTimeCardEn { + TranslationsStaffTimeCardEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Timecard' + String get title => 'Timecard'; + + /// en: 'Hours Worked' + String get hours_worked => 'Hours Worked'; + + /// en: 'Total Earnings' + String get total_earnings => 'Total Earnings'; + + /// en: 'Shift History' + String get shift_history => 'Shift History'; + + /// en: 'No shifts for this month' + String get no_shifts => 'No shifts for this month'; + + /// en: 'hours' + String get hours => 'hours'; + + /// en: '/hr' + String get per_hr => '/hr'; + + late final TranslationsStaffTimeCardStatusEn status = TranslationsStaffTimeCardStatusEn._(_root); +} + // Path: staff_authentication.get_started_page class TranslationsStaffAuthenticationGetStartedPageEn { TranslationsStaffAuthenticationGetStartedPageEn._(this._root); @@ -1691,6 +1724,27 @@ class TranslationsStaffShiftsTagsEn { String get no_experience => 'No experience'; } +// Path: staff_time_card.status +class TranslationsStaffTimeCardStatusEn { + TranslationsStaffTimeCardStatusEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + + /// en: 'Approved' + String get approved => 'Approved'; + + /// en: 'Disputed' + String get disputed => 'Disputed'; + + /// en: 'Paid' + String get paid => 'Paid'; + + /// en: 'Pending' + String get pending => 'Pending'; +} + // Path: staff_authentication.profile_setup_page.steps class TranslationsStaffAuthenticationProfileSetupPageStepsEn { TranslationsStaffAuthenticationProfileSetupPageStepsEn._(this._root); @@ -3113,6 +3167,19 @@ extension on Translations { 'staff_shifts.details.pending_time' => ({required Object time}) => 'Pending ${time} ago', 'staff_shifts.tags.immediate_start' => 'Immediate start', 'staff_shifts.tags.no_experience' => 'No experience', + 'staff_time_card.title' => 'Timecard', + 'staff_time_card.hours_worked' => 'Hours Worked', + 'staff_time_card.total_earnings' => 'Total Earnings', + 'staff_time_card.shift_history' => 'Shift History', + 'staff_time_card.no_shifts' => 'No shifts for this month', + 'staff_time_card.hours' => 'hours', + 'staff_time_card.per_hr' => '/hr', + 'staff_time_card.status.approved' => 'Approved', + 'staff_time_card.status.disputed' => 'Disputed', + 'staff_time_card.status.paid' => 'Paid', + _ => null, + } ?? switch (path) { + 'staff_time_card.status.pending' => 'Pending', _ => null, }; } diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/strings_es.g.dart b/apps/mobile/packages/core_localization/lib/src/l10n/strings_es.g.dart index 3a47abcd..f497959c 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/strings_es.g.dart +++ b/apps/mobile/packages/core_localization/lib/src/l10n/strings_es.g.dart @@ -53,6 +53,7 @@ class TranslationsEs with BaseTranslations implements T @override late final _TranslationsStaffCertificatesEs staff_certificates = _TranslationsStaffCertificatesEs._(_root); @override late final _TranslationsStaffProfileAttireEs staff_profile_attire = _TranslationsStaffProfileAttireEs._(_root); @override late final _TranslationsStaffShiftsEs staff_shifts = _TranslationsStaffShiftsEs._(_root); + @override late final _TranslationsStaffTimeCardEs staff_time_card = _TranslationsStaffTimeCardEs._(_root); } // Path: common @@ -292,6 +293,23 @@ class _TranslationsStaffShiftsEs implements TranslationsStaffShiftsEn { @override late final _TranslationsStaffShiftsTagsEs tags = _TranslationsStaffShiftsTagsEs._(_root); } +// Path: staff_time_card +class _TranslationsStaffTimeCardEs implements TranslationsStaffTimeCardEn { + _TranslationsStaffTimeCardEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get title => 'Tarjeta de tiempo'; + @override String get hours_worked => 'Horas trabajadas'; + @override String get total_earnings => 'Ganancias totales'; + @override String get shift_history => 'Historial de turnos'; + @override String get no_shifts => 'No hay turnos para este mes'; + @override String get hours => 'horas'; + @override String get per_hr => '/hr'; + @override late final _TranslationsStaffTimeCardStatusEs status = _TranslationsStaffTimeCardStatusEs._(_root); +} + // Path: staff_authentication.get_started_page class _TranslationsStaffAuthenticationGetStartedPageEs implements TranslationsStaffAuthenticationGetStartedPageEn { _TranslationsStaffAuthenticationGetStartedPageEs._(this._root); @@ -1049,6 +1067,19 @@ class _TranslationsStaffShiftsTagsEs implements TranslationsStaffShiftsTagsEn { @override String get no_experience => 'No experience'; } +// Path: staff_time_card.status +class _TranslationsStaffTimeCardStatusEs implements TranslationsStaffTimeCardStatusEn { + _TranslationsStaffTimeCardStatusEs._(this._root); + + final TranslationsEs _root; // ignore: unused_field + + // Translations + @override String get approved => 'Aprobado'; + @override String get disputed => 'Disputado'; + @override String get paid => 'Pagado'; + @override String get pending => 'Pendiente'; +} + // Path: staff_authentication.profile_setup_page.steps class _TranslationsStaffAuthenticationProfileSetupPageStepsEs implements TranslationsStaffAuthenticationProfileSetupPageStepsEn { _TranslationsStaffAuthenticationProfileSetupPageStepsEs._(this._root); @@ -2091,6 +2122,19 @@ extension on TranslationsEs { 'staff_shifts.details.pending_time' => ({required Object time}) => 'Pending ${time} ago', 'staff_shifts.tags.immediate_start' => 'Immediate start', 'staff_shifts.tags.no_experience' => 'No experience', + 'staff_time_card.title' => 'Tarjeta de tiempo', + 'staff_time_card.hours_worked' => 'Horas trabajadas', + 'staff_time_card.total_earnings' => 'Ganancias totales', + 'staff_time_card.shift_history' => 'Historial de turnos', + 'staff_time_card.no_shifts' => 'No hay turnos para este mes', + 'staff_time_card.hours' => 'horas', + 'staff_time_card.per_hr' => '/hr', + 'staff_time_card.status.approved' => 'Aprobado', + 'staff_time_card.status.disputed' => 'Disputado', + 'staff_time_card.status.paid' => 'Pagado', + _ => null, + } ?? switch (path) { + 'staff_time_card.status.pending' => 'Pendiente', _ => null, }; } diff --git a/apps/mobile/packages/features/staff/payments/lib/src/data/repositories_impl/payments_repository_impl.dart b/apps/mobile/packages/features/staff/payments/lib/src/data/repositories_impl/payments_repository_impl.dart index c599dfe5..5abcd80b 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/data/repositories_impl/payments_repository_impl.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/data/repositories_impl/payments_repository_impl.dart @@ -2,9 +2,16 @@ import 'package:krow_data_connect/krow_data_connect.dart'; import 'package:krow_domain/krow_domain.dart'; import '../../domain/repositories/payments_repository.dart'; +/// Implementation of [PaymentsRepository]. +/// +/// This class handles the retrieval of payment data by delegating to the +/// [FinancialRepositoryMock] from the data connect package. +/// +/// It resides in the data layer and depends on the domain layer for the repository interface. class PaymentsRepositoryImpl implements PaymentsRepository { final FinancialRepositoryMock financialRepository; + /// Creates a [PaymentsRepositoryImpl] with the given [financialRepository]. PaymentsRepositoryImpl({required this.financialRepository}); @override diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/arguments/get_payment_history_arguments.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/arguments/get_payment_history_arguments.dart new file mode 100644 index 00000000..a01acee4 --- /dev/null +++ b/apps/mobile/packages/features/staff/payments/lib/src/domain/arguments/get_payment_history_arguments.dart @@ -0,0 +1,12 @@ +import 'package:krow_core/core.dart'; + +/// Arguments for getting payment history. +class GetPaymentHistoryArguments extends UseCaseArgument { + /// The period to filter by (e.g., "monthly", "weekly"). + final String period; + + const GetPaymentHistoryArguments(this.period); + + @override + List get props => [period]; +} diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_summary.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_summary.dart deleted file mode 100644 index ad575833..00000000 --- a/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_summary.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:equatable/equatable.dart'; - -class PaymentSummary extends Equatable { - final double weeklyEarnings; - final double monthlyEarnings; - final double pendingEarnings; - final double totalEarnings; - - const PaymentSummary({ - required this.weeklyEarnings, - required this.monthlyEarnings, - required this.pendingEarnings, - required this.totalEarnings, - }); - - @override - List get props => [ - weeklyEarnings, - monthlyEarnings, - pendingEarnings, - totalEarnings, - ]; -} diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_transaction.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_transaction.dart deleted file mode 100644 index 0b0eb7b2..00000000 --- a/apps/mobile/packages/features/staff/payments/lib/src/domain/entities/payment_transaction.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:equatable/equatable.dart'; - -class PaymentTransaction extends Equatable { - final String id; - final String title; - final String location; - final String address; - final DateTime date; - final String workedTime; - final double amount; - final String status; - final int hours; - final double rate; - - const PaymentTransaction({ - required this.id, - required this.title, - required this.location, - required this.address, - required this.date, - required this.workedTime, - required this.amount, - required this.status, - required this.hours, - required this.rate, - }); - - @override - List get props => [ - id, - title, - location, - address, - date, - workedTime, - amount, - status, - hours, - rate, - ]; -} diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/repositories/payments_repository.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/repositories/payments_repository.dart index 3ce9d8ee..71546c9e 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/domain/repositories/payments_repository.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/domain/repositories/payments_repository.dart @@ -1,6 +1,10 @@ import 'package:krow_domain/krow_domain.dart'; +/// Repository interface for Payments feature. +/// +/// Defines the contract for data access related to staff payments. +/// Implementations of this interface should reside in the data layer. abstract class PaymentsRepository { - /// Fetches the list of payments. + /// Fetches the list of payments for the current staff member. Future> getPayments(); } diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_history_usecase.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_history_usecase.dart index 5686e3ef..d5a3a3a8 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_history_usecase.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_history_usecase.dart @@ -1,14 +1,19 @@ import 'package:krow_core/core.dart'; import 'package:krow_domain/krow_domain.dart'; +import '../arguments/get_payment_history_arguments.dart'; import '../repositories/payments_repository.dart'; -class GetPaymentHistoryUseCase extends UseCase> { +/// Use case to retrieve payment history filtered by a period. +/// +/// This use case delegates the data retrieval to [PaymentsRepository]. +class GetPaymentHistoryUseCase extends UseCase> { final PaymentsRepository repository; + /// Creates a [GetPaymentHistoryUseCase]. GetPaymentHistoryUseCase(this.repository); @override - Future> call(String period) async { + Future> call(GetPaymentHistoryArguments arguments) async { // TODO: Implement filtering by period return await repository.getPayments(); } diff --git a/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_summary_usecase.dart b/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_summary_usecase.dart index 3e9aff90..27b74290 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_summary_usecase.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/domain/usecases/get_payment_summary_usecase.dart @@ -2,9 +2,14 @@ import 'package:krow_core/core.dart'; import 'package:krow_domain/krow_domain.dart'; import '../repositories/payments_repository.dart'; +/// Use case to retrieve payment summary information. +/// +/// It fetches the full list of payments, which ideally should be aggregated +/// by the presentation layer or a specific data source method. class GetPaymentSummaryUseCase extends NoInputUseCase> { final PaymentsRepository repository; + /// Creates a [GetPaymentSummaryUseCase]. GetPaymentSummaryUseCase(this.repository); @override diff --git a/apps/mobile/packages/features/staff/payments/lib/src/presentation/blocs/payments/payments_bloc.dart b/apps/mobile/packages/features/staff/payments/lib/src/presentation/blocs/payments/payments_bloc.dart index fedaa862..33887032 100644 --- a/apps/mobile/packages/features/staff/payments/lib/src/presentation/blocs/payments/payments_bloc.dart +++ b/apps/mobile/packages/features/staff/payments/lib/src/presentation/blocs/payments/payments_bloc.dart @@ -1,5 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:krow_domain/krow_domain.dart'; +import '../../../domain/arguments/get_payment_history_arguments.dart'; import '../../../domain/usecases/get_payment_summary_usecase.dart'; import '../../../domain/usecases/get_payment_history_usecase.dart'; import '../../models/payment_stats.dart'; @@ -27,7 +28,9 @@ class PaymentsBloc extends Bloc { final List allPayments = await getPaymentSummary(); final PaymentStats stats = _calculateStats(allPayments); - final List history = await getPaymentHistory('week'); + final List history = await getPaymentHistory( + const GetPaymentHistoryArguments('week'), + ); emit(PaymentsLoaded( summary: stats, history: history, @@ -44,10 +47,10 @@ class PaymentsBloc extends Bloc { ) async { final PaymentsState currentState = state; if (currentState is PaymentsLoaded) { - if (currentState.activePeriod == event.period) return; - try { - final List newHistory = await getPaymentHistory(event.period); + final List newHistory = await getPaymentHistory( + GetPaymentHistoryArguments(event.period), + ); emit(currentState.copyWith( history: newHistory, activePeriod: event.period, diff --git a/apps/mobile/packages/features/staff/profile/lib/src/presentation/navigation/profile_navigator.dart b/apps/mobile/packages/features/staff/profile/lib/src/presentation/navigation/profile_navigator.dart index 13285adc..dde95472 100644 --- a/apps/mobile/packages/features/staff/profile/lib/src/presentation/navigation/profile_navigator.dart +++ b/apps/mobile/packages/features/staff/profile/lib/src/presentation/navigation/profile_navigator.dart @@ -63,7 +63,7 @@ extension ProfileNavigator on IModularNavigator { /// Navigates to the timecard page. void pushTimecard() { - pushNamed('/time-card'); + pushNamed('../time-card'); } /// Navigates to the FAQs page. diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart new file mode 100644 index 00000000..f1f7d3f4 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/data/repositories_impl/time_card_repository_impl.dart @@ -0,0 +1,64 @@ +import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:krow_domain/krow_domain.dart'; +import '../../domain/entities/time_card.dart'; +import '../../domain/repositories/time_card_repository.dart'; + +class TimeCardRepositoryImpl implements TimeCardRepository { + final ShiftsRepositoryMock shiftsRepository; + + TimeCardRepositoryImpl({required this.shiftsRepository}); + + @override + Future> getTimeCards(DateTime month) async { + // We use ShiftsRepositoryMock as it contains shift details (title, client, etc). + // In a real app, we might query 'TimeCards' directly or join Shift+Payment. + // For now, we simulate TimeCards from Shifts. + final List shifts = await shiftsRepository.getMyShifts(); + + // Map to TimeCard and filter by the requested month. + return shifts + .map((Shift shift) { + double hours = 8.0; + // Simple parse for mock + try { + // Assuming HH:mm + final int start = int.parse(shift.startTime.split(':')[0]); + final int end = int.parse(shift.endTime.split(':')[0]); + hours = (end - start).abs().toDouble(); + if (hours == 0) hours = 8.0; + } catch (_) {} + + return TimeCard( + id: shift.id, + shiftTitle: shift.title, + clientName: shift.clientName, + date: DateTime.tryParse(shift.date) ?? DateTime.now(), + startTime: shift.startTime, + endTime: shift.endTime, + totalHours: hours, + hourlyRate: shift.hourlyRate, + totalPay: hours * shift.hourlyRate, + status: _mapStatus(shift.status), + location: shift.location, + ); + }) + .where((TimeCard tc) => + tc.date.year == month.year && tc.date.month == month.month) + .toList(); + } + + TimeCardStatus _mapStatus(String? shiftStatus) { + if (shiftStatus == null) return TimeCardStatus.pending; + // Map shift status to TimeCardStatus + switch (shiftStatus.toLowerCase()) { + case 'confirmed': + return TimeCardStatus.pending; + case 'completed': + return TimeCardStatus.approved; + case 'paid': + return TimeCardStatus.paid; + default: + return TimeCardStatus.pending; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/arguments/get_time_cards_arguments.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/arguments/get_time_cards_arguments.dart new file mode 100644 index 00000000..e0d76152 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/arguments/get_time_cards_arguments.dart @@ -0,0 +1,12 @@ +import 'package:krow_core/core.dart'; + +/// Arguments for the GetTimeCardsUseCase. +class GetTimeCardsArguments extends UseCaseArgument { + /// The month to fetch time cards for. + final DateTime month; + + const GetTimeCardsArguments(this.month); + + @override + List get props => [month]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart new file mode 100644 index 00000000..2654ccf0 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart @@ -0,0 +1,56 @@ +import 'package:equatable/equatable.dart'; + +enum TimeCardStatus { + pending, + approved, + paid, + disputed; + + bool get isApproved => this == TimeCardStatus.approved; + bool get isPaid => this == TimeCardStatus.paid; + bool get isDisputed => this == TimeCardStatus.disputed; + bool get isPending => this == TimeCardStatus.pending; +} + +class TimeCard extends Equatable { + final String id; + final String shiftTitle; + final String clientName; + final DateTime date; + final String startTime; + final String endTime; + final double totalHours; + final double hourlyRate; + final double totalPay; + final TimeCardStatus status; + final String? location; + + 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 + List get props => [ + id, + shiftTitle, + clientName, + date, + startTime, + endTime, + totalHours, + hourlyRate, + totalPay, + status, + location, + ]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/repositories/time_card_repository.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/repositories/time_card_repository.dart new file mode 100644 index 00000000..c5147009 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/repositories/time_card_repository.dart @@ -0,0 +1,12 @@ +import '../entities/time_card.dart'; + +/// Repository interface for accessing time card data. +/// +/// This repository handles fetching time cards and related financial data +/// for the staff member. +abstract class TimeCardRepository { + /// Retrieves a list of [TimeCard]s for a specific month. + /// + /// [month] is a [DateTime] representing the month to filter by. + Future> getTimeCards(DateTime month); +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/usecases/get_time_cards_usecase.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/usecases/get_time_cards_usecase.dart new file mode 100644 index 00000000..00f207dd --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/usecases/get_time_cards_usecase.dart @@ -0,0 +1,19 @@ +import 'package:krow_core/core.dart'; +import '../../domain/entities/time_card.dart'; +import '../arguments/get_time_cards_arguments.dart'; +import '../repositories/time_card_repository.dart'; + +/// UseCase to retrieve time cards for a given month. +class GetTimeCardsUseCase extends UseCase> { + final TimeCardRepository repository; + + GetTimeCardsUseCase(this.repository); + + /// Executes the use case. + /// + /// Returns a list of [TimeCard]s for the specified month in [arguments]. + @override + Future> call(GetTimeCardsArguments arguments) { + return repository.getTimeCards(arguments.month); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_bloc.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_bloc.dart new file mode 100644 index 00000000..75d5adcf --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_bloc.dart @@ -0,0 +1,42 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import '../../domain/entities/time_card.dart'; +import '../../domain/arguments/get_time_cards_arguments.dart'; +import '../../domain/usecases/get_time_cards_usecase.dart'; + +part 'time_card_event.dart'; +part 'time_card_state.dart'; + +/// BLoC to manage Time Card state. +class TimeCardBloc extends Bloc { + final GetTimeCardsUseCase getTimeCards; + + TimeCardBloc({required this.getTimeCards}) : super(TimeCardInitial()) { + on(_onLoadTimeCards); + on(_onChangeMonth); + } + + /// Handles fetching time cards for the requested month. + Future _onLoadTimeCards(LoadTimeCards event, Emitter emit) async { + emit(TimeCardLoading()); + try { + final List cards = await getTimeCards(GetTimeCardsArguments(event.month)); + + final double totalHours = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalHours); + final double totalEarnings = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalPay); + + emit(TimeCardLoaded( + timeCards: cards, + selectedMonth: event.month, + totalHours: totalHours, + totalEarnings: totalEarnings, + )); + } catch (e) { + emit(TimeCardError(e.toString())); + } + } + + Future _onChangeMonth(ChangeMonth event, Emitter emit) async { + add(LoadTimeCards(event.month)); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_event.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_event.dart new file mode 100644 index 00000000..1cf7317a --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_event.dart @@ -0,0 +1,23 @@ +part of 'time_card_bloc.dart'; + +abstract class TimeCardEvent extends Equatable { + const TimeCardEvent(); + @override + List get props => []; +} + +class LoadTimeCards extends TimeCardEvent { + final DateTime month; + const LoadTimeCards(this.month); + + @override + List get props => [month]; +} + +class ChangeMonth extends TimeCardEvent { + final DateTime month; + const ChangeMonth(this.month); + + @override + List get props => [month]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_state.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_state.dart new file mode 100644 index 00000000..4d75b832 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/blocs/time_card_state.dart @@ -0,0 +1,32 @@ +part of 'time_card_bloc.dart'; + +abstract class TimeCardState extends Equatable { + const TimeCardState(); + @override + List get props => []; +} + +class TimeCardInitial extends TimeCardState {} +class TimeCardLoading extends TimeCardState {} +class TimeCardLoaded extends TimeCardState { + final List timeCards; + final DateTime selectedMonth; + final double totalHours; + final double totalEarnings; + + const TimeCardLoaded({ + required this.timeCards, + required this.selectedMonth, + required this.totalHours, + required this.totalEarnings, + }); + + @override + List get props => [timeCards, selectedMonth, totalHours, totalEarnings]; +} +class TimeCardError extends TimeCardState { + final String message; + const TimeCardError(this.message); + @override + List get props => [message]; +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart new file mode 100644 index 00000000..6805e7fa --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/pages/time_card_page.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:core_localization/core_localization.dart'; +import 'package:design_system/design_system.dart'; +import '../blocs/time_card_bloc.dart'; +import '../widgets/month_selector.dart'; +import '../widgets/shift_history_list.dart'; +import '../widgets/time_card_summary.dart'; + +/// The main page for displaying the staff time card. +class TimeCardPage extends StatefulWidget { + const TimeCardPage({super.key}); + + @override + State createState() => _TimeCardPageState(); +} + +class _TimeCardPageState extends State { + final TimeCardBloc _bloc = Modular.get(); + + @override + void initState() { + super.initState(); + _bloc.add(LoadTimeCards(DateTime.now())); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _bloc, + child: Scaffold( + backgroundColor: UiColors.bgPrimary, + appBar: AppBar( + backgroundColor: UiColors.bgPopup, + elevation: 0, + leading: IconButton( + icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary), + onPressed: () => Modular.to.pop(), + ), + title: Text( + t.staff_time_card.title, + style: UiTypography.headline4m.copyWith( + color: UiColors.textPrimary, + ), + ), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1.0), + child: Container(color: UiColors.border, height: 1.0), + ), + ), + body: BlocBuilder( + builder: (context, state) { + if (state is TimeCardLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TimeCardError) { + return Center(child: Text('Error: ${state.message}')); + } else if (state is TimeCardLoaded) { + return SingleChildScrollView( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space5, + vertical: UiConstants.space6, + ), + child: Column( + children: [ + MonthSelector( + selectedDate: state.selectedMonth, + onPreviousMonth: () => _bloc.add(ChangeMonth( + DateTime(state.selectedMonth.year, state.selectedMonth.month - 1), + )), + onNextMonth: () => _bloc.add(ChangeMonth( + DateTime(state.selectedMonth.year, state.selectedMonth.month + 1), + )), + ), + const SizedBox(height: UiConstants.space6), + TimeCardSummary( + totalHours: state.totalHours, + totalEarnings: state.totalEarnings, + ), + const SizedBox(height: UiConstants.space6), + ShiftHistoryList(timesheets: state.timeCards), + ], + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/month_selector.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/month_selector.dart new file mode 100644 index 00000000..f94a0485 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/month_selector.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:design_system/design_system.dart'; + +/// A widget that allows the user to navigate between months. +class MonthSelector extends StatelessWidget { + final DateTime selectedDate; + final VoidCallback onPreviousMonth; + final VoidCallback onNextMonth; + + const MonthSelector({ + super.key, + required this.selectedDate, + required this.onPreviousMonth, + required this.onNextMonth, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space1), + decoration: BoxDecoration( + color: UiColors.bgPopup, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(UiIcons.chevronLeft, color: UiColors.iconSecondary), + onPressed: onPreviousMonth, + ), + Text( + DateFormat('MMM yyyy').format(selectedDate), + style: UiTypography.title2b.copyWith( + color: UiColors.textPrimary, + ), + ), + IconButton( + icon: const Icon( + UiIcons.chevronRight, + color: UiColors.iconSecondary, + ), + onPressed: onNextMonth, + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/shift_history_list.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/shift_history_list.dart new file mode 100644 index 00000000..827e5273 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/shift_history_list.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; +import 'package:core_localization/core_localization.dart'; +import '../../domain/entities/time_card.dart'; +import 'timesheet_card.dart'; + +/// Displays the list of shift history or an empty state. +class ShiftHistoryList extends StatelessWidget { + final List timesheets; + + const ShiftHistoryList({super.key, required this.timesheets}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + t.staff_time_card.shift_history, + style: UiTypography.title2b.copyWith( + color: UiColors.textPrimary, + ), + ), + const SizedBox(height: UiConstants.space3), + if (timesheets.isEmpty) + Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: UiConstants.space12), + child: Column( + children: [ + const Icon(UiIcons.clock, size: 48, color: UiColors.iconSecondary), + const SizedBox(height: UiConstants.space3), + Text( + t.staff_time_card.no_shifts, + style: UiTypography.body1r.copyWith(color: UiColors.textSecondary), + ), + ], + ), + ), + ) + else + ...timesheets.map((ts) => TimesheetCard(timesheet: ts)), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/time_card_summary.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/time_card_summary.dart new file mode 100644 index 00000000..4b103490 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/time_card_summary.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:design_system/design_system.dart'; +import 'package:core_localization/core_localization.dart'; + +/// Displays the total hours worked and total earnings for the selected month. +class TimeCardSummary extends StatelessWidget { + final double totalHours; + final double totalEarnings; + + const TimeCardSummary({ + super.key, + required this.totalHours, + required this.totalEarnings, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _SummaryCard( + icon: UiIcons.clock, + label: t.staff_time_card.hours_worked, + value: totalHours.toStringAsFixed(1), + ), + ), + const SizedBox(width: UiConstants.space3), + Expanded( + child: _SummaryCard( + icon: UiIcons.dollar, + label: t.staff_time_card.total_earnings, + value: '\$${totalEarnings.toStringAsFixed(2)}', + ), + ), + ], + ); + } +} + +class _SummaryCard extends StatelessWidget { + final IconData icon; + final String label; + final String value; + + const _SummaryCard({ + required this.icon, + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.bgPopup, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 16, color: UiColors.primary), + const SizedBox(width: UiConstants.space2), + Text( + label, + style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), + ), + ], + ), + const SizedBox(height: UiConstants.space2), + Text( + value, + style: UiTypography.headline1m.copyWith( + color: UiColors.textPrimary, + ), + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart new file mode 100644 index 00000000..960c3619 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/presentation/widgets/timesheet_card.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:design_system/design_system.dart'; +import 'package:core_localization/core_localization.dart'; +import '../../domain/entities/time_card.dart'; + +/// A card widget displaying details of a single shift/timecard. +class TimesheetCard extends StatelessWidget { + final TimeCard timesheet; + + const TimesheetCard({super.key, required this.timesheet}); + + @override + Widget build(BuildContext context) { + final status = timesheet.status; + Color statusBg; + Color statusColor; + String statusText; + + switch (status) { + case TimeCardStatus.approved: + statusBg = UiColors.textSuccess.withOpacity(0.12); + statusColor = UiColors.textSuccess; + statusText = t.staff_time_card.status.approved; + break; + case TimeCardStatus.disputed: + statusBg = UiColors.destructive.withOpacity(0.12); + statusColor = UiColors.destructive; + statusText = t.staff_time_card.status.disputed; + break; + case TimeCardStatus.paid: + statusBg = UiColors.primary.withOpacity(0.12); + statusColor = UiColors.primary; + statusText = t.staff_time_card.status.paid; + break; + case TimeCardStatus.pending: + statusBg = UiColors.textWarning.withOpacity(0.12); + statusColor = UiColors.textWarning; + statusText = t.staff_time_card.status.pending; + break; + } + + final dateStr = DateFormat('EEE, MMM d').format(timesheet.date); + + return Container( + margin: const EdgeInsets.only(bottom: UiConstants.space3), + padding: const EdgeInsets.all(UiConstants.space4), + decoration: BoxDecoration( + color: UiColors.bgPopup, + borderRadius: UiConstants.radiusLg, + border: Border.all(color: UiColors.border), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + timesheet.shiftTitle, + style: UiTypography.body1m.copyWith( + color: UiColors.textPrimary, + ), + ), + Text( + timesheet.clientName, + style: UiTypography.body2r.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: UiConstants.space1, + ), + decoration: BoxDecoration( + color: statusBg, + borderRadius: UiConstants.radiusFull, + ), + child: Text( + statusText, + style: UiTypography.titleUppercase4b.copyWith( + color: statusColor, + ), + ), + ), + ], + ), + const SizedBox(height: UiConstants.space3), + Wrap( + spacing: UiConstants.space3, + runSpacing: UiConstants.space1, + children: [ + _IconText(icon: UiIcons.calendar, text: dateStr), + _IconText( + icon: UiIcons.clock, + text: '${_formatTime(timesheet.startTime)} - ${_formatTime(timesheet.endTime)}', + ), + if (timesheet.location != null) + _IconText(icon: UiIcons.mapPin, text: timesheet.location!), + ], + ), + const SizedBox(height: UiConstants.space3), + Container( + padding: const EdgeInsets.only(top: UiConstants.space3), + decoration: const BoxDecoration( + border: Border(top: BorderSide(color: UiColors.border)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${timesheet.totalHours.toStringAsFixed(1)} ${t.staff_time_card.hours} @ \$${timesheet.hourlyRate.toStringAsFixed(2)}${t.staff_time_card.per_hr}', + style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + ), + Text( + '\$${timesheet.totalPay.toStringAsFixed(2)}', + style: UiTypography.title2b.copyWith( + color: UiColors.primary, + ), + ), + ], + ), + ), + ], + ), + ); + } + + // Helper to safely format time strings like "HH:mm" + String _formatTime(String t) { + if (t.isEmpty) return '--:--'; + try { + final parts = t.split(':'); + if (parts.length >= 2) { + final dt = DateTime(2000, 1, 1, int.parse(parts[0]), int.parse(parts[1])); + return DateFormat('h:mm a').format(dt); + } + return t; + } catch (_) { + return t; + } + } +} + +class _IconText extends StatelessWidget { + final IconData icon; + final String text; + + const _IconText({required this.icon, required this.text}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14, color: UiColors.iconSecondary), + const SizedBox(width: UiConstants.space1), + Text( + text, + style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), + ), + ], + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart new file mode 100644 index 00000000..2819cdb9 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/staff_time_card_module.dart @@ -0,0 +1,35 @@ +library staff_time_card; + +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart'; + +import 'data/repositories_impl/time_card_repository_impl.dart'; +import 'domain/repositories/time_card_repository.dart'; +import 'domain/usecases/get_time_cards_usecase.dart'; +import 'presentation/blocs/time_card_bloc.dart'; +import 'presentation/pages/time_card_page.dart'; + +export 'presentation/pages/time_card_page.dart'; + +class StaffTimeCardModule extends Module { + @override + void binds(Injector i) { + // Repositories + // In a real app, ShiftsRepository might be provided by a Core Data Module. + // For this self-contained feature/mock, we instantiate it here if not available globally. + // Assuming we need a local instance for the mock to work or it's stateless. + i.add(ShiftsRepositoryMock.new); + i.add(TimeCardRepositoryImpl.new); + + // UseCases + i.add(GetTimeCardsUseCase.new); + + // Blocs + i.add(TimeCardBloc.new); + } + + @override + void routes(RouteManager r) { + r.child('/', child: (context) => const TimeCardPage()); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/staff_time_card.dart b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/staff_time_card.dart new file mode 100644 index 00000000..d6d3cda8 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/staff_time_card.dart @@ -0,0 +1 @@ +export 'src/staff_time_card_module.dart'; \ No newline at end of file diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/pubspec.yaml new file mode 100644 index 00000000..d74dc6b2 --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/finances/time_card/pubspec.yaml @@ -0,0 +1,31 @@ +name: staff_time_card +description: Staff Time Card Feature +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + flutter: ">=3.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_modular: ^6.3.2 + flutter_bloc: ^8.1.3 + equatable: ^2.0.5 + lucide_icons: ^0.257.0 + intl: ^0.20.0 + design_system: + path: ../../../../../design_system + core_localization: + path: ../../../../../core_localization + krow_core: + path: ../../../../../core + krow_domain: + path: ../../../../../domain + krow_data_connect: + path: ../../../../../data_connect + +dev_dependencies: + flutter_test: + sdk: flutter diff --git a/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart b/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart index 223c03c4..43e41c1a 100644 --- a/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart +++ b/apps/mobile/packages/features/staff/staff_main/lib/src/staff_main_module.dart @@ -12,6 +12,7 @@ import 'package:staff_certificates/staff_certificates.dart'; import 'package:staff_attire/staff_attire.dart'; import 'package:staff_shifts/staff_shifts.dart'; import 'package:staff_payments/staff_payements.dart'; +import 'package:staff_time_card/staff_time_card.dart'; import 'package:staff_main/src/presentation/blocs/staff_main_cubit.dart'; import 'package:staff_main/src/presentation/constants/staff_main_routes.dart'; @@ -67,5 +68,9 @@ class StaffMainModule extends Module { '/certificates', module: StaffCertificatesModule(), ); + r.module( + '/time-card', + module: StaffTimeCardModule(), + ); } } diff --git a/apps/mobile/packages/features/staff/staff_main/pubspec.yaml b/apps/mobile/packages/features/staff/staff_main/pubspec.yaml index 616df90c..47e0e85e 100644 --- a/apps/mobile/packages/features/staff/staff_main/pubspec.yaml +++ b/apps/mobile/packages/features/staff/staff_main/pubspec.yaml @@ -47,6 +47,8 @@ dependencies: path: ../shifts staff_payments: path: ../payments + staff_time_card: + path: ../profile_sections/finances/time_card dev_dependencies: flutter_test: diff --git a/apps/mobile/pubspec.lock b/apps/mobile/pubspec.lock index 3fb0cc1a..c8e7684c 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -1121,6 +1121,13 @@ packages: relative: true source: path version: "0.0.1" + staff_time_card: + dependency: transitive + description: + path: "packages/features/staff/profile_sections/finances/time_card" + relative: true + source: path + version: "0.0.1" stream_channel: dependency: transitive description: