From c113b836f2d738af259b0dd1eb37ad2bb224d9e9 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Thu, 26 Feb 2026 17:16:52 -0500 Subject: [PATCH] feat: Introduce `DocumentSelectedCard` and refactor `DocumentFileSelector` for improved display of selected documents, and update upload success navigation. --- .../documents/IMPLEMENTATION_WORKFLOW.md | 17 ++++++- .../pages/document_upload_page.dart | 2 +- .../document_file_selector.dart | 28 ++++++----- .../document_selected_card.dart | 46 +++++++++++++++++++ .../compliance/documents/pubspec.yaml | 1 - 5 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_selected_card.dart diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md index 9c6ea627..2c71be01 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/IMPLEMENTATION_WORKFLOW.md @@ -46,12 +46,25 @@ The workflow follows a 4-step lifecycle: #### UI — `DocumentUploadPage` - Accepts `StaffDocument document` and optional `String? initialUrl` as route arguments - PDF file picker via `FilePickerService.pickFile(allowedExtensions: ['pdf'])` -- File selector card: shows file name when selected or a prompt icon when empty +- **File selector card**: displays the file name with a document icon when selected, otherwise a prompt to upload. +- *Note:* PDF preview/opening functionality is explicitly omitted to maintain a clean, minimal upload flow without relying on external native viewers. - Attestation checkbox must be checked before the submit button is enabled - Loading state: shows `CircularProgressIndicator` while uploading (replaces button — mirrors attire pattern) -- On success: shows `UiSnackbar` and calls `Modular.to.pop(updatedDocument)` to return data to caller +- On success: shows `UiSnackbar` and calls `Modular.to.toDocuments()` to return to the list - On failure: shows `UiSnackbar` with error message; stays on page for retry +#### UI Guidelines (For Documents & Certificates) +To ensure a consistent experience across all compliance uploads (documents, certificates), adhere to the following UI patterns: +1. **Header & Instructions:** Use `UiAppBar` with the item name as the title and description as the subtitle. Provide clear instructions at the top of the body (`UiTypography.body1m.textPrimary`). +2. **File Selection Card:** + - When empty: Show a neutral/primary bordered card inviting the user to pick a file. + - When selected: Show an elegant card with `UiColors.bgPopup`, rounded corners (`UiConstants.radiusLg`), bordered by `UiColors.primary`. + - The selected card must contain an identifying icon, the truncated file name, and an explicit inline action (e.g., "Replace" or "Upload") using `UiTypography.body3m.textSecondary`. + - **Do not** embed native PDF viewers or link out to external readers. +3. **Bottom Footer / Attestation:** + - Fix the attestation checkbox and the submit button to the bottom using `bottomNavigationBar` wrapped in a `SafeArea` and `Padding`. + - The submit button state must be tightly coupled to both the file presence and the attestation state. + #### Module Wiring — `StaffDocumentsModule` - `UploadDocumentUseCase` bound as lazy singleton - `DocumentUploadCubit` bound (non-singleton, per-use) diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/pages/document_upload_page.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/pages/document_upload_page.dart index b9eee85a..975fee10 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/pages/document_upload_page.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/pages/document_upload_page.dart @@ -61,7 +61,7 @@ class _DocumentUploadPageState extends State { message: t.staff_documents.upload.success, type: UiSnackbarType.success, ); - Modular.to.pop(state.updatedDocument); + Modular.to.toDocuments(); } else if (state.status == DocumentUploadStatus.failure) { UiSnackbar.show( context, diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_file_selector.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_file_selector.dart index f21c379f..4c112749 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_file_selector.dart +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_file_selector.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; // ignore: depend_on_referenced_packages import 'package:core_localization/core_localization.dart'; +import 'document_selected_card.dart'; + /// Displays a tappable card that prompts the user to pick a PDF file. /// /// Shows the selected file name when a file has been chosen, or an @@ -24,6 +26,14 @@ class DocumentFileSelector extends StatelessWidget { @override Widget build(BuildContext context) { + if (_hasFile) { + return InkWell( + onTap: onTap, + borderRadius: UiConstants.radiusLg, + child: DocumentSelectedCard(selectedFilePath: selectedFilePath!), + ); + } + return InkWell( onTap: onTap, borderRadius: UiConstants.radiusLg, @@ -32,27 +42,21 @@ class DocumentFileSelector extends StatelessWidget { decoration: BoxDecoration( color: UiColors.bgPopup, borderRadius: UiConstants.radiusLg, - border: Border.all( - color: _hasFile ? UiColors.primary : UiColors.border, - ), + border: Border.all(color: UiColors.border), ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - _hasFile ? UiIcons.file : UiIcons.upload, + const Icon( + UiIcons.upload, size: 48, - color: _hasFile ? UiColors.primary : UiColors.textSecondary, + color: UiColors.textSecondary, ), const SizedBox(height: UiConstants.space2), Text( - _hasFile - ? selectedFilePath!.split('/').last - : t.staff_documents.upload.select_pdf, - style: UiTypography.body2m.copyWith( - color: _hasFile ? UiColors.primary : UiColors.textSecondary, - ), + t.staff_documents.upload.select_pdf, + style: UiTypography.body2m.textError, textAlign: TextAlign.center, ), ], diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_selected_card.dart b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_selected_card.dart new file mode 100644 index 00000000..5a27ce1c --- /dev/null +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/lib/src/presentation/widgets/document_upload/document_selected_card.dart @@ -0,0 +1,46 @@ +import 'package:design_system/design_system.dart'; +import 'package:flutter/material.dart'; + +/// A card that displays the selected document file name and an icon. +class DocumentSelectedCard extends StatelessWidget { + const DocumentSelectedCard({super.key, required this.selectedFilePath}); + + /// The local path of the currently selected file. + final String selectedFilePath; + + @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.primary), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: UiColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(UiConstants.space2), + ), + child: const Center( + child: Icon(UiIcons.file, color: UiColors.primary, size: 20), + ), + ), + const SizedBox(width: UiConstants.space3), + Expanded( + child: Text( + selectedFilePath.split('/').last, + style: UiTypography.body1m.textPrimary, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} diff --git a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/pubspec.yaml b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/pubspec.yaml index c7f1438f..b099e9da 100644 --- a/apps/mobile/packages/features/staff/profile_sections/compliance/documents/pubspec.yaml +++ b/apps/mobile/packages/features/staff/profile_sections/compliance/documents/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: equatable: ^2.0.5 firebase_auth: ^6.1.4 firebase_data_connect: ^0.2.2+2 - # Architecture Packages design_system: path: ../../../../../design_system