diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index fc4d87f9..f64d4c82 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -45,6 +45,7 @@ export 'src/entities/skills/skill_kit.dart'; // Financial & Payroll export 'src/entities/financial/invoice.dart'; +export 'src/entities/financial/time_card.dart'; export 'src/entities/financial/invoice_item.dart'; export 'src/entities/financial/invoice_decline.dart'; export 'src/entities/financial/staff_payment.dart'; diff --git a/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart b/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart new file mode 100644 index 00000000..572d74a1 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/adapters/financial/time_card_adapter.dart @@ -0,0 +1,50 @@ +import '../../entities/financial/time_card.dart'; + +/// Adapter for [TimeCard] to map data layer values to domain entity. +class TimeCardAdapter { + /// Maps primitive values to [TimeCard]. + static TimeCard fromPrimitives({ + required String id, + required String shiftTitle, + required String clientName, + required DateTime date, + required String startTime, + required String endTime, + required double totalHours, + required double hourlyRate, + required double totalPay, + required String status, + String? location, + }) { + return TimeCard( + id: id, + shiftTitle: shiftTitle, + clientName: clientName, + date: date, + startTime: startTime, + endTime: endTime, + totalHours: totalHours, + hourlyRate: hourlyRate, + totalPay: totalPay, + status: _stringToStatus(status), + location: location, + ); + } + + static TimeCardStatus _stringToStatus(String status) { + switch (status.toUpperCase()) { + case 'CHECKED_OUT': + case 'COMPLETED': + return TimeCardStatus.approved; // Assuming completed = approved for now + case 'PAID': + return TimeCardStatus.paid; // If this status exists + case 'DISPUTED': + return TimeCardStatus.disputed; + case 'CHECKED_IN': + case 'ACCEPTED': + case 'CONFIRMED': + default: + return TimeCardStatus.pending; + } + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart b/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart similarity index 62% rename from apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart rename to apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart index 2654ccf0..77bcb4ae 100644 --- a/apps/mobile/packages/features/staff/profile_sections/finances/time_card/lib/src/domain/entities/time_card.dart +++ b/apps/mobile/packages/domain/lib/src/entities/financial/time_card.dart @@ -1,30 +1,52 @@ import 'package:equatable/equatable.dart'; +/// Status of a time card. enum TimeCardStatus { + /// Waiting for approval or payment. pending, + /// Approved by manager. approved, + /// Payment has been issued. paid, + /// Disputed by staff or client. disputed; + /// Whether the card is approved. bool get isApproved => this == TimeCardStatus.approved; + /// Whether the card is paid. bool get isPaid => this == TimeCardStatus.paid; + /// Whether the card is disputed. bool get isDisputed => this == TimeCardStatus.disputed; + /// Whether the card is pending. bool get isPending => this == TimeCardStatus.pending; } +/// Represents a time card for a staff member. class TimeCard extends Equatable { + /// Unique identifier of the time card (often matches Application ID). final String id; + /// Title of the shift. final String shiftTitle; + /// Name of the client business. final String clientName; + /// Date of the shift. final DateTime date; + /// Actual or scheduled start time. final String startTime; + /// Actual or scheduled end time. final String endTime; + /// Total hours worked. final double totalHours; + /// Hourly pay rate. final double hourlyRate; + /// Total pay amount. final double totalPay; + /// Current status of the time card. final TimeCardStatus status; + /// Location name. final String? location; + /// Creates a [TimeCard]. const TimeCard({ required this.id, required this.shiftTitle, 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 index f1f7d3f4..e99adbec 100644 --- 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 @@ -1,64 +1,78 @@ -import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; +import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc; +import 'package:intl/intl.dart'; +import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart'; -import '../../domain/entities/time_card.dart'; +// ignore: implementation_imports +import 'package:krow_domain/src/adapters/financial/time_card_adapter.dart'; import '../../domain/repositories/time_card_repository.dart'; +/// Implementation of [TimeCardRepository] using Firebase Data Connect. class TimeCardRepositoryImpl implements TimeCardRepository { - final ShiftsRepositoryMock shiftsRepository; + final dc.ExampleConnector _dataConnect; + final firebase.FirebaseAuth _firebaseAuth; - TimeCardRepositoryImpl({required this.shiftsRepository}); + /// Creates a [TimeCardRepositoryImpl]. + TimeCardRepositoryImpl({ + required dc.ExampleConnector dataConnect, + required firebase.FirebaseAuth firebaseAuth, + }) : _dataConnect = dataConnect, + _firebaseAuth = firebaseAuth; + + Future _getStaffId() async { + final firebase.User? user = _firebaseAuth.currentUser; + if (user == null) throw Exception('User not authenticated'); + + final fdc.QueryResult result = + await _dataConnect.getStaffByUserId(userId: user.uid).execute(); + if (result.data.staffs.isEmpty) { + throw Exception('Staff profile not found'); + } + return result.data.staffs.first.id; + } @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(); + final String staffId = await _getStaffId(); + // Fetch applications. Limit can be adjusted, assuming 100 is safe for now. + final fdc.QueryResult result = + await _dataConnect.getApplicationsByStaffId(staffId: staffId).limit(100).execute(); - // 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 result.data.applications + .where((dc.GetApplicationsByStaffIdApplications app) { + final DateTime? shiftDate = app.shift.date?.toDateTime(); + if (shiftDate == null) return false; + return shiftDate.year == month.year && shiftDate.month == month.month; + }) + .map((dc.GetApplicationsByStaffIdApplications app) { + final DateTime shiftDate = app.shift.date!.toDateTime(); + final String startTime = _formatTime(app.checkInTime) ?? _formatTime(app.shift.startTime) ?? ''; + final String endTime = _formatTime(app.checkOutTime) ?? _formatTime(app.shift.endTime) ?? ''; - return TimeCard( - id: shift.id, - shiftTitle: shift.title, - clientName: shift.clientName, - date: DateTime.tryParse(shift.date) ?? DateTime.now(), - startTime: shift.startTime, - endTime: shift.endTime, + // Prefer shiftRole values for pay/hours + final double hours = app.shiftRole.hours ?? 0.0; + final double rate = app.shiftRole.role.costPerHour; + final double pay = app.shiftRole.totalValue ?? 0.0; + + return TimeCardAdapter.fromPrimitives( + id: app.id, + shiftTitle: app.shift.title, + clientName: app.shift.order.business.businessName, + date: shiftDate, + startTime: startTime, + endTime: endTime, totalHours: hours, - hourlyRate: shift.hourlyRate, - totalPay: hours * shift.hourlyRate, - status: _mapStatus(shift.status), - location: shift.location, + hourlyRate: rate, + totalPay: pay, + status: app.status.stringValue, + location: app.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; - } + String? _formatTime(fdc.Timestamp? timestamp) { + if (timestamp == null) return null; + return DateFormat('HH:mm').format(timestamp.toDateTime()); } } 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 index c5147009..c44f86e4 100644 --- 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 @@ -1,4 +1,4 @@ -import '../entities/time_card.dart'; +import 'package:krow_domain/krow_domain.dart'; /// Repository interface for accessing time card data. /// 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 index 00f207dd..1ee76890 100644 --- 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 @@ -1,5 +1,5 @@ import 'package:krow_core/core.dart'; -import '../../domain/entities/time_card.dart'; +import 'package:krow_domain/krow_domain.dart'; import '../arguments/get_time_cards_arguments.dart'; import '../repositories/time_card_repository.dart'; 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 index 75d5adcf..e655755e 100644 --- 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 @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; -import '../../domain/entities/time_card.dart'; +import 'package:krow_domain/krow_domain.dart'; import '../../domain/arguments/get_time_cards_arguments.dart'; import '../../domain/usecases/get_time_cards_usecase.dart'; 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 index 827e5273..0135e0cb 100644 --- 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 @@ -1,7 +1,7 @@ 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 'package:krow_domain/krow_domain.dart'; import 'timesheet_card.dart'; /// Displays the list of shift history or an empty state. 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 index 960c3619..4e8d7351 100644 --- 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 @@ -2,7 +2,7 @@ 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'; +import 'package:krow_domain/krow_domain.dart'; /// A card widget displaying details of a single shift/timecard. class TimesheetCard extends StatelessWidget { 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 index 2819cdb9..4f7e7856 100644 --- 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 @@ -1,5 +1,6 @@ library staff_time_card; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; @@ -11,15 +12,23 @@ import 'presentation/pages/time_card_page.dart'; export 'presentation/pages/time_card_page.dart'; +/// Module for the Staff Time Card feature. +/// +/// This module configures dependency injection for accessing time card data, +/// including the repositories, use cases, and BLoCs. class StaffTimeCardModule extends Module { + @override + List get imports => [DataConnectModule()]; + @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); + i.add( + () => TimeCardRepositoryImpl( + dataConnect: ExampleConnector.instance, + firebaseAuth: FirebaseAuth.instance, + ), + ); // UseCases i.add(GetTimeCardsUseCase.new);