feat: Implement attire item filtering and refactor attire capture flow and repository logic

This commit is contained in:
Achintha Isuru
2026-02-25 22:18:25 -05:00
parent c7c505f743
commit 083744cd34
6 changed files with 38 additions and 25 deletions

View File

@@ -196,7 +196,7 @@ extension StaffNavigator on IModularNavigator {
/// ///
/// Record sizing and appearance information for uniform allocation. /// Record sizing and appearance information for uniform allocation.
void toAttire() { void toAttire() {
pushNamed(StaffPaths.attire); navigate(StaffPaths.attire);
} }
/// Pushes the attire capture page. /// Pushes the attire capture page.

View File

@@ -36,6 +36,10 @@ class AttireRepositoryImpl implements AttireRepository {
@override @override
Future<AttireItem> uploadPhoto(String itemId, String filePath) async { Future<AttireItem> uploadPhoto(String itemId, String filePath) async {
// 6. Return updated AttireItem by re-fetching to get the PENDING/SUCCESS status
final List<AttireItem> finalOptions = await _connector.getAttireOptions();
return finalOptions.firstWhere((AttireItem e) => e.id == itemId);
// 1. Upload file to Core API // 1. Upload file to Core API
final FileUploadService uploadService = Modular.get<FileUploadService>(); final FileUploadService uploadService = Modular.get<FileUploadService>();
final FileUploadResponse uploadRes = await uploadService.uploadFile( final FileUploadResponse uploadRes = await uploadService.uploadFile(
@@ -104,8 +108,8 @@ class AttireRepositoryImpl implements AttireRepository {
); );
// 6. Return updated AttireItem by re-fetching to get the PENDING/SUCCESS status // 6. Return updated AttireItem by re-fetching to get the PENDING/SUCCESS status
final List<AttireItem> finalOptions = await _connector.getAttireOptions(); // final List<AttireItem> finalOptions = await _connector.getAttireOptions();
return finalOptions.firstWhere((AttireItem e) => e.id == itemId); // return finalOptions.firstWhere((AttireItem e) => e.id == itemId);
} }
AttireVerificationStatus _mapToAttireStatus(VerificationStatus status) { AttireVerificationStatus _mapToAttireStatus(VerificationStatus status) {

View File

@@ -64,6 +64,10 @@ class AttireCubit extends Cubit<AttireState>
emit(state.copyWith(selectedIds: currentSelection)); emit(state.copyWith(selectedIds: currentSelection));
} }
void updateFilter(String filter) {
emit(state.copyWith(filter: filter));
}
void syncCapturedPhoto(AttireItem item) { void syncCapturedPhoto(AttireItem item) {
// Update the options list with the new item data // Update the options list with the new item data
final List<AttireItem> updatedOptions = state.options final List<AttireItem> updatedOptions = state.options

View File

@@ -9,12 +9,14 @@ class AttireState extends Equatable {
this.options = const <AttireItem>[], this.options = const <AttireItem>[],
this.selectedIds = const <String>[], this.selectedIds = const <String>[],
this.photoUrls = const <String, String>{}, this.photoUrls = const <String, String>{},
this.filter = 'All',
this.errorMessage, this.errorMessage,
}); });
final AttireStatus status; final AttireStatus status;
final List<AttireItem> options; final List<AttireItem> options;
final List<String> selectedIds; final List<String> selectedIds;
final Map<String, String> photoUrls; final Map<String, String> photoUrls;
final String filter;
final String? errorMessage; final String? errorMessage;
/// Helper to check if item is mandatory /// Helper to check if item is mandatory
@@ -44,11 +46,20 @@ class AttireState extends Equatable {
bool get canSave => allMandatorySelected && allMandatoryHavePhotos; bool get canSave => allMandatorySelected && allMandatoryHavePhotos;
List<AttireItem> get filteredOptions {
return options.where((AttireItem item) {
if (filter == 'Required') return item.isMandatory;
if (filter == 'Non-Essential') return !item.isMandatory;
return true;
}).toList();
}
AttireState copyWith({ AttireState copyWith({
AttireStatus? status, AttireStatus? status,
List<AttireItem>? options, List<AttireItem>? options,
List<String>? selectedIds, List<String>? selectedIds,
Map<String, String>? photoUrls, Map<String, String>? photoUrls,
String? filter,
String? errorMessage, String? errorMessage,
}) { }) {
return AttireState( return AttireState(
@@ -56,6 +67,7 @@ class AttireState extends Equatable {
options: options ?? this.options, options: options ?? this.options,
selectedIds: selectedIds ?? this.selectedIds, selectedIds: selectedIds ?? this.selectedIds,
photoUrls: photoUrls ?? this.photoUrls, photoUrls: photoUrls ?? this.photoUrls,
filter: filter ?? this.filter,
errorMessage: errorMessage, errorMessage: errorMessage,
); );
} }
@@ -66,6 +78,7 @@ class AttireState extends Equatable {
options, options,
selectedIds, selectedIds,
photoUrls, photoUrls,
filter,
errorMessage, errorMessage,
]; ];
} }

View File

@@ -203,6 +203,15 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
type: UiSnackbarType.error, type: UiSnackbarType.error,
); );
} }
if (state.status == AttireCaptureStatus.success) {
UiSnackbar.show(
context,
message: 'Attire image submitted for verification',
type: UiSnackbarType.success,
);
Modular.to.toAttire();
}
}, },
builder: (BuildContext context, AttireCaptureState state) { builder: (BuildContext context, AttireCaptureState state) {
final String? currentPhotoUrl = final String? currentPhotoUrl =

View File

@@ -12,16 +12,9 @@ import '../widgets/attire_filter_chips.dart';
import '../widgets/attire_info_card.dart'; import '../widgets/attire_info_card.dart';
import '../widgets/attire_item_card.dart'; import '../widgets/attire_item_card.dart';
class AttirePage extends StatefulWidget { class AttirePage extends StatelessWidget {
const AttirePage({super.key}); const AttirePage({super.key});
@override
State<AttirePage> createState() => _AttirePageState();
}
class _AttirePageState extends State<AttirePage> {
String _filter = 'All';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final AttireCubit cubit = Modular.get<AttireCubit>(); final AttireCubit cubit = Modular.get<AttireCubit>();
@@ -30,6 +23,7 @@ class _AttirePageState extends State<AttirePage> {
appBar: UiAppBar( appBar: UiAppBar(
title: t.staff_profile_attire.title, title: t.staff_profile_attire.title,
showBackButton: true, showBackButton: true,
onLeadingPressed: () => Modular.to.toProfile(),
), ),
body: BlocProvider<AttireCubit>.value( body: BlocProvider<AttireCubit>.value(
value: cubit, value: cubit,
@@ -48,14 +42,7 @@ class _AttirePageState extends State<AttirePage> {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
final List<AttireItem> options = state.options; final List<AttireItem> filteredOptions = state.filteredOptions;
final List<AttireItem> filteredOptions = options.where((
AttireItem item,
) {
if (_filter == 'Required') return item.isMandatory;
if (_filter == 'Non-Essential') return !item.isMandatory;
return true;
}).toList();
return Column( return Column(
children: <Widget>[ children: <Widget>[
@@ -70,12 +57,8 @@ class _AttirePageState extends State<AttirePage> {
// Filter Chips // Filter Chips
AttireFilterChips( AttireFilterChips(
selectedFilter: _filter, selectedFilter: state.filter,
onFilterChanged: (String value) { onFilterChanged: cubit.updateFilter,
setState(() {
_filter = value;
});
},
), ),
const SizedBox(height: UiConstants.space6), const SizedBox(height: UiConstants.space6),