feat: Implement staff attire management including fetching options, user attire status, and upserting attire details.
This commit is contained in:
@@ -11,9 +11,8 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
/// Creates a new [StaffConnectorRepositoryImpl].
|
||||
///
|
||||
/// Requires a [DataConnectService] instance for backend communication.
|
||||
StaffConnectorRepositoryImpl({
|
||||
DataConnectService? service,
|
||||
}) : _service = service ?? DataConnectService.instance;
|
||||
StaffConnectorRepositoryImpl({DataConnectService? service})
|
||||
: _service = service ?? DataConnectService.instance;
|
||||
|
||||
final DataConnectService _service;
|
||||
|
||||
@@ -22,15 +21,17 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffProfileCompletionData,
|
||||
GetStaffProfileCompletionVariables> response =
|
||||
await _service.connector
|
||||
.getStaffProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
GetStaffProfileCompletionData,
|
||||
GetStaffProfileCompletionVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.getStaffProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
|
||||
final GetStaffProfileCompletionStaff? staff = response.data.staff;
|
||||
final List<GetStaffProfileCompletionEmergencyContacts>
|
||||
emergencyContacts = response.data.emergencyContacts;
|
||||
final List<GetStaffProfileCompletionEmergencyContacts> emergencyContacts =
|
||||
response.data.emergencyContacts;
|
||||
final List<GetStaffProfileCompletionTaxForms> taxForms =
|
||||
response.data.taxForms;
|
||||
|
||||
@@ -43,11 +44,13 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffPersonalInfoCompletionData,
|
||||
GetStaffPersonalInfoCompletionVariables> response =
|
||||
await _service.connector
|
||||
.getStaffPersonalInfoCompletion(id: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
GetStaffPersonalInfoCompletionData,
|
||||
GetStaffPersonalInfoCompletionVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.getStaffPersonalInfoCompletion(id: staffId)
|
||||
.execute();
|
||||
|
||||
final GetStaffPersonalInfoCompletionStaff? staff = response.data.staff;
|
||||
|
||||
@@ -60,11 +63,13 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffEmergencyProfileCompletionData,
|
||||
GetStaffEmergencyProfileCompletionVariables> response =
|
||||
await _service.connector
|
||||
.getStaffEmergencyProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
GetStaffEmergencyProfileCompletionData,
|
||||
GetStaffEmergencyProfileCompletionVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.getStaffEmergencyProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
|
||||
return response.data.emergencyContacts.isNotEmpty;
|
||||
});
|
||||
@@ -75,11 +80,13 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffExperienceProfileCompletionData,
|
||||
GetStaffExperienceProfileCompletionVariables> response =
|
||||
await _service.connector
|
||||
.getStaffExperienceProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
GetStaffExperienceProfileCompletionData,
|
||||
GetStaffExperienceProfileCompletionVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.getStaffExperienceProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
|
||||
final GetStaffExperienceProfileCompletionStaff? staff =
|
||||
response.data.staff;
|
||||
@@ -93,11 +100,13 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffTaxFormsProfileCompletionData,
|
||||
GetStaffTaxFormsProfileCompletionVariables> response =
|
||||
await _service.connector
|
||||
.getStaffTaxFormsProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
GetStaffTaxFormsProfileCompletionData,
|
||||
GetStaffTaxFormsProfileCompletionVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.getStaffTaxFormsProfileCompletion(id: staffId)
|
||||
.execute();
|
||||
|
||||
return response.data.taxForms.isNotEmpty;
|
||||
});
|
||||
@@ -135,9 +144,7 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
final bool hasExperience =
|
||||
(skills is List && skills.isNotEmpty) ||
|
||||
(industries is List && industries.isNotEmpty);
|
||||
return emergencyContacts.isNotEmpty &&
|
||||
taxForms.isNotEmpty &&
|
||||
hasExperience;
|
||||
return emergencyContacts.isNotEmpty && taxForms.isNotEmpty && hasExperience;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -146,14 +153,10 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<GetStaffByIdData, GetStaffByIdVariables> response =
|
||||
await _service.connector
|
||||
.getStaffById(id: staffId)
|
||||
.execute();
|
||||
await _service.connector.getStaffById(id: staffId).execute();
|
||||
|
||||
if (response.data.staff == null) {
|
||||
throw const ServerException(
|
||||
technicalMessage: 'Staff not found',
|
||||
);
|
||||
throw const ServerException(technicalMessage: 'Staff not found');
|
||||
}
|
||||
|
||||
final GetStaffByIdStaff rawStaff = response.data.staff!;
|
||||
@@ -183,11 +186,13 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
final QueryResult<ListBenefitsDataByStaffIdData,
|
||||
ListBenefitsDataByStaffIdVariables> response =
|
||||
await _service.connector
|
||||
.listBenefitsDataByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
final QueryResult<
|
||||
ListBenefitsDataByStaffIdData,
|
||||
ListBenefitsDataByStaffIdVariables
|
||||
>
|
||||
response = await _service.connector
|
||||
.listBenefitsDataByStaffId(staffId: staffId)
|
||||
.execute();
|
||||
|
||||
return response.data.benefitsDatas.map((data) {
|
||||
final plan = data.vendorBenefitPlan;
|
||||
@@ -200,6 +205,56 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<AttireItem>> getAttireOptions() async {
|
||||
return _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
// Fetch all options
|
||||
final QueryResult<ListAttireOptionsData, void> optionsResponse =
|
||||
await _service.connector.listAttireOptions().execute();
|
||||
|
||||
// Fetch user's attire status
|
||||
final QueryResult<GetStaffAttireData, GetStaffAttireVariables>
|
||||
attiresResponse = await _service.connector
|
||||
.getStaffAttire(staffId: staffId)
|
||||
.execute();
|
||||
|
||||
final Map<String, GetStaffAttireStaffAttires> attireMap = {
|
||||
for (final item in attiresResponse.data.staffAttires)
|
||||
item.attireOptionId: item,
|
||||
};
|
||||
|
||||
return optionsResponse.data.attireOptions.map((e) {
|
||||
final GetStaffAttireStaffAttires? userAttire = attireMap[e.id];
|
||||
return AttireItem(
|
||||
id: e.itemId,
|
||||
label: e.label,
|
||||
description: e.description,
|
||||
imageUrl: e.imageUrl,
|
||||
isMandatory: e.isMandatory ?? false,
|
||||
verificationStatus: userAttire?.verificationStatus?.stringValue,
|
||||
photoUrl: userAttire?.verificationPhotoUrl,
|
||||
);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upsertStaffAttire({
|
||||
required String attireOptionId,
|
||||
required String photoUrl,
|
||||
}) async {
|
||||
await _service.run(() async {
|
||||
final String staffId = await _service.getStaffId();
|
||||
|
||||
await _service.connector
|
||||
.upsertStaffAttire(staffId: staffId, attireOptionId: attireOptionId)
|
||||
.verificationPhotoUrl(photoUrl)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
try {
|
||||
@@ -210,4 +265,3 @@ class StaffConnectorRepositoryImpl implements StaffConnectorRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,17 @@ abstract interface class StaffConnectorRepository {
|
||||
/// Returns a list of [Benefit] entities.
|
||||
Future<List<Benefit>> getBenefits();
|
||||
|
||||
/// Fetches the attire options for the current authenticated user.
|
||||
///
|
||||
/// Returns a list of [AttireItem] entities.
|
||||
Future<List<AttireItem>> getAttireOptions();
|
||||
|
||||
/// Upserts staff attire photo information.
|
||||
Future<void> upsertStaffAttire({
|
||||
required String attireOptionId,
|
||||
required String photoUrl,
|
||||
});
|
||||
|
||||
/// Signs out the current user.
|
||||
///
|
||||
/// Clears the user's session and authentication state.
|
||||
|
||||
@@ -11,6 +11,8 @@ class AttireItem extends Equatable {
|
||||
this.description,
|
||||
this.imageUrl,
|
||||
this.isMandatory = false,
|
||||
this.verificationStatus,
|
||||
this.photoUrl,
|
||||
});
|
||||
|
||||
/// Unique identifier of the attire item.
|
||||
@@ -28,6 +30,12 @@ class AttireItem extends Equatable {
|
||||
/// Whether this item is mandatory for onboarding.
|
||||
final bool isMandatory;
|
||||
|
||||
/// The current verification status of the uploaded photo.
|
||||
final String? verificationStatus;
|
||||
|
||||
/// The URL of the photo uploaded by the staff member.
|
||||
final String? photoUrl;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
id,
|
||||
@@ -35,5 +43,7 @@ class AttireItem extends Equatable {
|
||||
description,
|
||||
imageUrl,
|
||||
isMandatory,
|
||||
verificationStatus,
|
||||
photoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
@@ -6,34 +5,19 @@ import '../../domain/repositories/attire_repository.dart';
|
||||
|
||||
/// Implementation of [AttireRepository].
|
||||
///
|
||||
/// Delegates data access to [DataConnectService].
|
||||
/// Delegates data access to [StaffConnectorRepository].
|
||||
class AttireRepositoryImpl implements AttireRepository {
|
||||
/// Creates an [AttireRepositoryImpl].
|
||||
AttireRepositoryImpl({DataConnectService? service})
|
||||
: _service = service ?? DataConnectService.instance;
|
||||
AttireRepositoryImpl({StaffConnectorRepository? connector})
|
||||
: _connector =
|
||||
connector ?? DataConnectService.instance.getStaffRepository();
|
||||
|
||||
/// The Data Connect service.
|
||||
final DataConnectService _service;
|
||||
/// The Staff Connector repository.
|
||||
final StaffConnectorRepository _connector;
|
||||
|
||||
@override
|
||||
Future<List<AttireItem>> getAttireOptions() async {
|
||||
return _service.run(() async {
|
||||
final QueryResult<ListAttireOptionsData, void> result = await _service
|
||||
.connector
|
||||
.listAttireOptions()
|
||||
.execute();
|
||||
return result.data.attireOptions
|
||||
.map(
|
||||
(ListAttireOptionsAttireOptions e) => AttireItem(
|
||||
id: e.itemId,
|
||||
label: e.label,
|
||||
description: e.description,
|
||||
imageUrl: e.imageUrl,
|
||||
isMandatory: e.isMandatory ?? false,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
});
|
||||
return _connector.getAttireOptions();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -41,16 +25,22 @@ class AttireRepositoryImpl implements AttireRepository {
|
||||
required List<String> selectedItemIds,
|
||||
required Map<String, String> photoUrls,
|
||||
}) async {
|
||||
// TODO: Connect to actual backend mutation when available.
|
||||
// For now, simulate network delay as per prototype behavior.
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
// We already upsert photos in uploadPhoto (to follow the new flow).
|
||||
// This could save selections if there was a separate "SelectedAttire" table.
|
||||
// For now, it's a no-op as the source of truth is the StaffAttire table.
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> uploadPhoto(String itemId) async {
|
||||
// TODO: Connect to actual storage service/mutation when available.
|
||||
// For now, simulate upload delay and return mock URL.
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
return 'mock_url_for_$itemId';
|
||||
// In a real app, this would upload to Firebase Storage first.
|
||||
// Since the prototype returns a mock URL, we'll use that to upsert our record.
|
||||
final String mockUrl = 'mock_url_for_$itemId';
|
||||
|
||||
await _connector.upsertStaffAttire(
|
||||
attireOptionId: itemId,
|
||||
photoUrl: mockUrl,
|
||||
);
|
||||
|
||||
return mockUrl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,18 +23,17 @@ class AttireCubit extends Cubit<AttireState>
|
||||
action: () async {
|
||||
final List<AttireItem> options = await _getAttireOptionsUseCase();
|
||||
|
||||
// Auto-select mandatory items initially as per prototype
|
||||
final List<String> mandatoryIds = options
|
||||
.where((AttireItem e) => e.isMandatory)
|
||||
.map((AttireItem e) => e.id)
|
||||
.toList();
|
||||
// Extract photo URLs and selection status from backend data
|
||||
final Map<String, String> photoUrls = <String, String>{};
|
||||
final List<String> selectedIds = <String>[];
|
||||
|
||||
final List<String> initialSelection = List<String>.from(
|
||||
state.selectedIds,
|
||||
);
|
||||
for (final String id in mandatoryIds) {
|
||||
if (!initialSelection.contains(id)) {
|
||||
initialSelection.add(id);
|
||||
for (final AttireItem item in options) {
|
||||
if (item.photoUrl != null) {
|
||||
photoUrls[item.id] = item.photoUrl!;
|
||||
}
|
||||
// If mandatory or has photo, consider it selected initially
|
||||
if (item.isMandatory || item.photoUrl != null) {
|
||||
selectedIds.add(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +41,8 @@ class AttireCubit extends Cubit<AttireState>
|
||||
state.copyWith(
|
||||
status: AttireStatus.success,
|
||||
options: options,
|
||||
selectedIds: initialSelection,
|
||||
selectedIds: selectedIds,
|
||||
photoUrls: photoUrls,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -65,20 +65,8 @@ class AttireCubit extends Cubit<AttireState>
|
||||
}
|
||||
|
||||
void syncCapturedPhoto(String itemId, String url) {
|
||||
final Map<String, String> currentPhotos = Map<String, String>.from(
|
||||
state.photoUrls,
|
||||
);
|
||||
currentPhotos[itemId] = url;
|
||||
|
||||
// Auto-select item on upload success if not selected
|
||||
final List<String> currentSelection = List<String>.from(state.selectedIds);
|
||||
if (!currentSelection.contains(itemId)) {
|
||||
currentSelection.add(itemId);
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(photoUrls: currentPhotos, selectedIds: currentSelection),
|
||||
);
|
||||
// When a photo is captured, we refresh the options to get the updated status from backend
|
||||
loadOptions();
|
||||
}
|
||||
|
||||
Future<void> save() async {
|
||||
|
||||
@@ -70,14 +70,22 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
builder: (BuildContext context, AttireCaptureState state) {
|
||||
final bool isUploading =
|
||||
state.status == AttireCaptureStatus.uploading;
|
||||
final bool hasPhoto =
|
||||
state.photoUrl != null || widget.initialPhotoUrl != null;
|
||||
final String statusText = hasPhoto
|
||||
? 'Pending Verification'
|
||||
: 'Not Uploaded';
|
||||
final Color statusColor = hasPhoto
|
||||
? UiColors.textWarning
|
||||
: UiColors.textInactive;
|
||||
final String? currentPhotoUrl =
|
||||
state.photoUrl ?? widget.initialPhotoUrl;
|
||||
final bool hasUploadedPhoto = currentPhotoUrl != null;
|
||||
|
||||
final String statusText =
|
||||
widget.item.verificationStatus ??
|
||||
(hasUploadedPhoto
|
||||
? 'Pending Verification'
|
||||
: 'Not Uploaded');
|
||||
|
||||
final Color statusColor =
|
||||
widget.item.verificationStatus == 'SUCCESS'
|
||||
? UiColors.textPrimary
|
||||
: (hasUploadedPhoto
|
||||
? UiColors.textWarning
|
||||
: UiColors.textInactive);
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
@@ -86,21 +94,54 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
// Image Preview
|
||||
AttireImagePreview(imageUrl: widget.item.imageUrl),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
// Image Preview (Toggle between example and uploaded)
|
||||
if (hasUploadedPhoto) ...<Widget>[
|
||||
Text(
|
||||
'Your Uploaded Photo',
|
||||
style: UiTypography.body1b.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
AttireImagePreview(imageUrl: currentPhotoUrl),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
'Reference Example',
|
||||
style: UiTypography.body2b.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space1),
|
||||
Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
child: Image.network(
|
||||
widget.item.imageUrl ?? '',
|
||||
height: 120,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) =>
|
||||
const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...<Widget>[
|
||||
AttireImagePreview(
|
||||
imageUrl: widget.item.imageUrl,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
'Example of the item that you need to upload.',
|
||||
style: UiTypography.body1b.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
|
||||
Text(
|
||||
'Example of the item that you need to upload.',
|
||||
style: UiTypography.body1b.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text(
|
||||
widget.item.description ?? '',
|
||||
style: UiTypography.body1r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
if (widget.item.description != null)
|
||||
Text(
|
||||
widget.item.description!,
|
||||
style: UiTypography.body1r.textSecondary,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
|
||||
// Verification info
|
||||
AttireVerificationStatusCard(
|
||||
@@ -118,15 +159,19 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
if (isUploading)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (!hasPhoto ||
|
||||
true) // Show options even if has photo (allows re-upload)
|
||||
const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(UiConstants.space8),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
else
|
||||
AttireUploadButtons(onUpload: _onUpload),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hasPhoto)
|
||||
if (hasUploadedPhoto)
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
@@ -135,7 +180,7 @@ class _AttireCapturePageState extends State<AttireCapturePage> {
|
||||
child: UiButton.primary(
|
||||
text: 'Submit Image',
|
||||
onPressed: () {
|
||||
Modular.to.pop(state.photoUrl);
|
||||
Modular.to.pop(currentPhotoUrl);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -80,36 +80,59 @@ class _AttirePageState extends State<AttirePage> {
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Item List
|
||||
...filteredOptions.map((AttireItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
if (filteredOptions.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: AttireItemCard(
|
||||
item: item,
|
||||
isUploading: false,
|
||||
uploadedPhotoUrl: state.photoUrls[item.id],
|
||||
onTap: () async {
|
||||
final String? resultUrl =
|
||||
await Navigator.push<String?>(
|
||||
context,
|
||||
MaterialPageRoute<String?>(
|
||||
builder: (BuildContext ctx) =>
|
||||
AttireCapturePage(
|
||||
item: item,
|
||||
initialPhotoUrl:
|
||||
state.photoUrls[item.id],
|
||||
),
|
||||
),
|
||||
);
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.shirt,
|
||||
size: 48,
|
||||
color: UiColors.iconInactive,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
'No items found for this filter.',
|
||||
style: UiTypography.body1m.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...filteredOptions.map((AttireItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: AttireItemCard(
|
||||
item: item,
|
||||
isUploading: false,
|
||||
uploadedPhotoUrl: state.photoUrls[item.id],
|
||||
onTap: () async {
|
||||
final String? resultUrl =
|
||||
await Navigator.push<String?>(
|
||||
context,
|
||||
MaterialPageRoute<String?>(
|
||||
builder: (BuildContext ctx) =>
|
||||
AttireCapturePage(
|
||||
item: item,
|
||||
initialPhotoUrl:
|
||||
state.photoUrls[item.id],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (resultUrl != null && mounted) {
|
||||
cubit.syncCapturedPhoto(item.id, resultUrl);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
if (resultUrl != null && mounted) {
|
||||
cubit.syncCapturedPhoto(item.id, resultUrl);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: UiConstants.space20),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -18,9 +18,8 @@ class AttireItemCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool hasPhoto = uploadedPhotoUrl != null;
|
||||
|
||||
final String statusText = hasPhoto ? 'Pending' : 'Not Uploaded';
|
||||
final bool hasPhoto = item.photoUrl != null;
|
||||
final String statusText = item.verificationStatus ?? 'Not Uploaded';
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
@@ -85,7 +84,9 @@ class AttireItemCard extends StatelessWidget {
|
||||
UiChip(
|
||||
label: statusText,
|
||||
size: UiChipSize.xSmall,
|
||||
variant: UiChipVariant.secondary,
|
||||
variant: item.verificationStatus == 'SUCCESS'
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -105,9 +106,13 @@ class AttireItemCard extends StatelessWidget {
|
||||
size: 24,
|
||||
)
|
||||
else if (hasPhoto && !isUploading)
|
||||
const Icon(
|
||||
UiIcons.check,
|
||||
color: UiColors.textWarning,
|
||||
Icon(
|
||||
item.verificationStatus == 'SUCCESS'
|
||||
? UiIcons.check
|
||||
: UiIcons.clock,
|
||||
color: item.verificationStatus == 'SUCCESS'
|
||||
? UiColors.textPrimary
|
||||
: UiColors.textWarning,
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user