feat(shifts): implement submit for approval functionality

- Added `submitForApproval` method to `ShiftsRepositoryInterface` and its implementation in `ShiftsRepositoryImpl`.
- Created `SubmitForApprovalUseCase` to handle the submission logic.
- Updated `ShiftsBloc` to handle `SubmitForApprovalEvent` and manage submission state.
- Enhanced `HistoryShiftsTab` and `MyShiftsTab` to support submission actions and display appropriate UI feedback.
- Refactored date utilities for better calendar management and filtering of past shifts.
- Improved UI components for better spacing and alignment.
- Localized success messages for shift submission actions.
This commit is contained in:
Achintha Isuru
2026-03-18 14:37:55 -04:00
parent 3e5b6af8dc
commit 3a5f2cc9c6
50 changed files with 1269 additions and 408 deletions

View File

@@ -42,6 +42,9 @@ export 'src/services/session/client_session_store.dart';
export 'src/services/session/staff_session_store.dart';
export 'src/services/session/v2_session_service.dart';
// Auth
export 'src/services/auth/auth_token_provider.dart';
// Device Services
export 'src/services/device/camera/camera_service.dart';
export 'src/services/device/gallery/gallery_service.dart';

View File

@@ -3,6 +3,9 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:image_picker/image_picker.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:krow_core/src/services/auth/auth_token_provider.dart';
import 'package:krow_core/src/services/auth/firebase_auth_token_provider.dart';
import '../core.dart';
/// A module that provides core services and shared dependencies.
@@ -57,7 +60,10 @@ class CoreModule extends Module {
),
);
// 6. Register Geofence Device Services
// 6. Auth Token Provider
i.addLazySingleton<AuthTokenProvider>(FirebaseAuthTokenProvider.new);
// 7. Register Geofence Device Services
i.addLazySingleton<LocationService>(() => const LocationService());
i.addLazySingleton<NotificationService>(() => NotificationService());
i.addLazySingleton<StorageService>(() => StorageService());

View File

@@ -48,6 +48,26 @@ abstract final class ClientEndpoints {
static const ApiEndpoint coverageCoreTeam =
ApiEndpoint('/client/coverage/core-team');
/// Coverage incidents.
static const ApiEndpoint coverageIncidents =
ApiEndpoint('/client/coverage/incidents');
/// Blocked staff.
static const ApiEndpoint coverageBlockedStaff =
ApiEndpoint('/client/coverage/blocked-staff');
/// Coverage swap requests.
static const ApiEndpoint coverageSwapRequests =
ApiEndpoint('/client/coverage/swap-requests');
/// Dispatch teams.
static const ApiEndpoint coverageDispatchTeams =
ApiEndpoint('/client/coverage/dispatch-teams');
/// Dispatch candidates.
static const ApiEndpoint coverageDispatchCandidates =
ApiEndpoint('/client/coverage/dispatch-candidates');
/// Hubs list.
static const ApiEndpoint hubs = ApiEndpoint('/client/hubs');
@@ -162,4 +182,28 @@ abstract final class ClientEndpoints {
/// Cancel late worker assignment.
static ApiEndpoint coverageCancelLateWorker(String assignmentId) =>
ApiEndpoint('/client/coverage/late-workers/$assignmentId/cancel');
/// Register or delete device push token (POST to register, DELETE to remove).
static const ApiEndpoint devicesPushTokens =
ApiEndpoint('/client/devices/push-tokens');
/// Create shift manager.
static const ApiEndpoint shiftManagerCreate =
ApiEndpoint('/client/shift-managers');
/// Resolve coverage swap request by ID.
static ApiEndpoint coverageSwapRequestResolve(String id) =>
ApiEndpoint('/client/coverage/swap-requests/$id/resolve');
/// Cancel coverage swap request by ID.
static ApiEndpoint coverageSwapRequestCancel(String id) =>
ApiEndpoint('/client/coverage/swap-requests/$id/cancel');
/// Create dispatch team membership.
static const ApiEndpoint coverageDispatchTeamMembershipsCreate =
ApiEndpoint('/client/coverage/dispatch-teams/memberships');
/// Delete dispatch team membership by ID.
static ApiEndpoint coverageDispatchTeamMembershipsDelete(String id) =>
ApiEndpoint('/client/coverage/dispatch-teams/memberships/$id');
}

View File

@@ -2,39 +2,42 @@ import 'package:krow_domain/krow_domain.dart' show ApiEndpoint;
/// Core infrastructure endpoints (upload, signed URLs, LLM, verifications,
/// rapid orders).
///
/// Paths are at the unified API root level (not under `/core/`).
abstract final class CoreEndpoints {
/// Upload a file.
static const ApiEndpoint uploadFile =
ApiEndpoint('/core/upload-file');
static const ApiEndpoint uploadFile = ApiEndpoint('/upload-file');
/// Create a signed URL for a file.
static const ApiEndpoint createSignedUrl =
ApiEndpoint('/core/create-signed-url');
static const ApiEndpoint createSignedUrl = ApiEndpoint('/create-signed-url');
/// Invoke a Large Language Model.
static const ApiEndpoint invokeLlm = ApiEndpoint('/core/invoke-llm');
static const ApiEndpoint invokeLlm = ApiEndpoint('/invoke-llm');
/// Root for verification operations.
static const ApiEndpoint verifications =
ApiEndpoint('/core/verifications');
static const ApiEndpoint verifications = ApiEndpoint('/verifications');
/// Get status of a verification job.
static ApiEndpoint verificationStatus(String id) =>
ApiEndpoint('/core/verifications/$id');
ApiEndpoint('/verifications/$id');
/// Review a verification decision.
static ApiEndpoint verificationReview(String id) =>
ApiEndpoint('/core/verifications/$id/review');
ApiEndpoint('/verifications/$id/review');
/// Retry a verification job.
static ApiEndpoint verificationRetry(String id) =>
ApiEndpoint('/core/verifications/$id/retry');
ApiEndpoint('/verifications/$id/retry');
/// Transcribe audio to text for rapid orders.
static const ApiEndpoint transcribeRapidOrder =
ApiEndpoint('/core/rapid-orders/transcribe');
ApiEndpoint('/rapid-orders/transcribe');
/// Parse text to structured rapid order.
static const ApiEndpoint parseRapidOrder =
ApiEndpoint('/core/rapid-orders/parse');
ApiEndpoint('/rapid-orders/parse');
/// Combined transcribe + parse in a single call.
static const ApiEndpoint processRapidOrder =
ApiEndpoint('/rapid-orders/process');
}

View File

@@ -105,6 +105,10 @@ abstract final class StaffEndpoints {
/// Benefits.
static const ApiEndpoint benefits = ApiEndpoint('/staff/profile/benefits');
/// Benefits history.
static const ApiEndpoint benefitsHistory =
ApiEndpoint('/staff/profile/benefits/history');
/// Time card.
static const ApiEndpoint timeCard =
ApiEndpoint('/staff/profile/time-card');
@@ -112,6 +116,10 @@ abstract final class StaffEndpoints {
/// Privacy settings.
static const ApiEndpoint privacy = ApiEndpoint('/staff/profile/privacy');
/// Preferred locations.
static const ApiEndpoint locations =
ApiEndpoint('/staff/profile/locations');
/// FAQs.
static const ApiEndpoint faqs = ApiEndpoint('/staff/faqs');
@@ -177,4 +185,16 @@ abstract final class StaffEndpoints {
/// Delete certificate by ID.
static ApiEndpoint certificateDelete(String certificateId) =>
ApiEndpoint('/staff/profile/certificates/$certificateId');
/// Submit shift for approval.
static ApiEndpoint shiftSubmitForApproval(String shiftId) =>
ApiEndpoint('/staff/shifts/$shiftId/submit-for-approval');
/// Location streams.
static const ApiEndpoint locationStreams =
ApiEndpoint('/staff/location-streams');
/// Register or delete device push token (POST to register, DELETE to remove).
static const ApiEndpoint devicesPushTokens =
ApiEndpoint('/staff/devices/push-tokens');
}

View File

@@ -0,0 +1,11 @@
/// Provides the current Firebase ID token for API authentication.
///
/// Lives in core so feature packages can access auth tokens
/// without importing firebase_auth directly.
abstract interface class AuthTokenProvider {
/// Returns the current ID token, refreshing if expired.
///
/// Pass [forceRefresh] to force a token refresh from Firebase.
/// Returns null if no user is signed in.
Future<String?> getIdToken({bool forceRefresh});
}

View File

@@ -0,0 +1,15 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:krow_core/src/services/auth/auth_token_provider.dart';
/// Firebase-backed implementation of [AuthTokenProvider].
///
/// Delegates to [FirebaseAuth] to get the current user's
/// ID token. Must run in the main isolate (Firebase SDK requirement).
class FirebaseAuthTokenProvider implements AuthTokenProvider {
@override
Future<String?> getIdToken({bool forceRefresh = false}) async {
final User? user = FirebaseAuth.instance.currentUser;
return user?.getIdToken(forceRefresh);
}
}