From 341d77949904008003885418c2cb36e654def5ee Mon Sep 17 00:00:00 2001 From: Vaibhav Surve Date: Fri, 28 Nov 2025 15:34:13 +0530 Subject: [PATCH] Refactor service project job handling and improve tag management --- .../add_service_project_job_controller.dart | 24 +- lib/helpers/services/api_service.dart | 32 +- lib/helpers/widgets/custom_app_bar.dart | 80 ++--- .../add_service_project_job_bottom_sheet.dart | 15 +- lib/view/finance/advance_payment_screen.dart | 13 - lib/view/layouts/user_profile_right_bar.dart | 37 +++ .../service_project_job_detail_screen.dart | 286 ++++++++++-------- 7 files changed, 278 insertions(+), 209 deletions(-) diff --git a/lib/controller/service_project/add_service_project_job_controller.dart b/lib/controller/service_project/add_service_project_job_controller.dart index 3d07097..084647d 100644 --- a/lib/controller/service_project/add_service_project_job_controller.dart +++ b/lib/controller/service_project/add_service_project_job_controller.dart @@ -63,26 +63,34 @@ class AddServiceProjectJobController extends GetxController { return; } - final assigneeIds = selectedAssignees.map((e) => e.id).toList(); - isLoading.value = true; - final success = await ApiService.createServiceProjectJobApi( + final jobId = await ApiService.createServiceProjectJobApi( title: titleCtrl.text.trim(), description: descCtrl.text.trim(), projectId: projectId, - branchId: selectedBranch.value?.id, - assignees: assigneeIds.map((id) => {"id": id}).toList(), + branchId: selectedBranch.value?.id, + assignees: selectedAssignees // payload mapping + .map((e) => {"employeeId": e.id, "isActive": true}) + .toList(), startDate: startDate.value!, dueDate: dueDate.value!, - tags: enteredTags.map((tag) => {"name": tag}).toList(), + tags: enteredTags + .map((tag) => {"id": null, "name": tag, "isActive": true}) + .toList(), ); isLoading.value = false; - if (success) { + if (jobId != null) { if (Get.isRegistered()) { - Get.find().refreshJobsAfterAdd(); + final detailsCtrl = Get.find(); + + // 🔥 1. Refresh job LIST + detailsCtrl.refreshJobsAfterAdd(); + + // 🔥 2. Refresh job DETAILS (FULL DATA - including tags and assignees) + await detailsCtrl.fetchJobDetail(jobId); } Get.back(); diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index 2c22e46..95cd499 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -595,8 +595,7 @@ class ApiService { return null; } - /// Create a new Service Project Job - static Future createServiceProjectJobApi({ + static Future createServiceProjectJobApi({ required String title, required String description, required String projectId, @@ -623,32 +622,22 @@ class ApiService { try { final response = await _postRequest(endpoint, body); - if (response == null) { - logSafe("Create Service Project Job failed: null response", - level: LogLevel.error); - return false; - } - - logSafe( - "Create Service Project Job response status: ${response.statusCode}"); - logSafe("Create Service Project Job response body: ${response.body}"); + if (response == null) return null; final json = jsonDecode(response.body); + if (json['success'] == true) { - logSafe("Service Project Job created successfully: ${json['data']}"); - return true; - } else { - logSafe( - "Failed to create Service Project Job: ${json['message'] ?? 'Unknown error'}", - level: LogLevel.warning, - ); - return false; + final jobId = json['data']?['id']; + logSafe("Service Project Job created successfully: $jobId"); + return jobId; } + + return null; } catch (e, stack) { logSafe("Exception during createServiceProjectJobApi: $e", level: LogLevel.error); logSafe("StackTrace: $stack", level: LogLevel.debug); - return false; + return null; } } @@ -671,8 +660,7 @@ class ApiService { 'pageNumber': pageNumber.toString(), 'pageSize': pageSize.toString(), 'isActive': isActive.toString(), - if (isArchive) - 'isArchive': 'true', + if (isArchive) 'isArchive': 'true', }; final response = await _getRequest(endpoint, queryParams: queryParams); diff --git a/lib/helpers/widgets/custom_app_bar.dart b/lib/helpers/widgets/custom_app_bar.dart index 5128802..fbcd2e0 100644 --- a/lib/helpers/widgets/custom_app_bar.dart +++ b/lib/helpers/widgets/custom_app_bar.dart @@ -54,44 +54,50 @@ class CustomAppBar extends StatelessWidget ), title: Padding( padding: MySpacing.only(right: horizontalPadding, left: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, + child: Row( children: [ - MyText.titleLarge( - title, - fontWeight: 800, - color: onPrimaryColor, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - MySpacing.height(3), - GetBuilder( - builder: (projectController) { - final displayProjectName = projectName ?? - projectController.selectedProject?.name ?? - 'Select Project'; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.folder_open, - size: 14, color: onPrimaryColor), - MySpacing.width(4), - Flexible( - child: MyText.bodySmall( - displayProjectName, - fontWeight: 500, - color: onPrimaryColor.withOpacity(0.8), - overflow: TextOverflow.ellipsis, - ), - ), - MySpacing.width(2), - const Icon(Icons.keyboard_arrow_down, - size: 18, color: onPrimaryColor), - ], - ); - }, + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + MyText.titleLarge( + title, + fontWeight: 800, + color: onPrimaryColor, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + MySpacing.height(3), + GetBuilder( + builder: (projectController) { + final displayProjectName = projectName ?? + projectController.selectedProject?.name ?? + 'Select Project'; + return Row( + children: [ + const Icon(Icons.folder_open, + size: 14, color: onPrimaryColor), + MySpacing.width(4), + Flexible( + child: MyText.bodySmall( + displayProjectName, + fontWeight: 500, + color: onPrimaryColor.withOpacity(0.8), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + MySpacing.width(2), + const Icon(Icons.keyboard_arrow_down, + size: 18, color: onPrimaryColor), + ], + ); + }, + ), + ], + ), ), ], ), diff --git a/lib/model/service_project/add_service_project_job_bottom_sheet.dart b/lib/model/service_project/add_service_project_job_bottom_sheet.dart index e6a1162..8c3b139 100644 --- a/lib/model/service_project/add_service_project_job_bottom_sheet.dart +++ b/lib/model/service_project/add_service_project_job_bottom_sheet.dart @@ -220,8 +220,9 @@ class _AddServiceProjectJobBottomSheetState .where((s) => s.isNotEmpty); for (final p in parts) { - if (!controller.enteredTags.contains(p)) { - controller.enteredTags.add(p); + final clean = p.replaceAll('_', ' '); + if (!controller.enteredTags.contains(clean)) { + controller.enteredTags.add(clean); } } } @@ -239,8 +240,9 @@ class _AddServiceProjectJobBottomSheetState .where((s) => s.isNotEmpty); for (final p in parts) { - if (!controller.enteredTags.contains(p)) { - controller.enteredTags.add(p); + final clean = p.replaceAll('_', ' '); + if (!controller.enteredTags.contains(clean)) { + controller.enteredTags.add(clean); } } controller.tagCtrl.clear(); @@ -256,8 +258,9 @@ class _AddServiceProjectJobBottomSheetState .where((s) => s.isNotEmpty); for (final p in parts) { - if (!controller.enteredTags.contains(p)) { - controller.enteredTags.add(p); + final clean = p.replaceAll('_', ' '); + if (!controller.enteredTags.contains(clean)) { + controller.enteredTags.add(clean); } } controller.tagCtrl.clear(); diff --git a/lib/view/finance/advance_payment_screen.dart b/lib/view/finance/advance_payment_screen.dart index 7fa2795..0926452 100644 --- a/lib/view/finance/advance_payment_screen.dart +++ b/lib/view/finance/advance_payment_screen.dart @@ -324,9 +324,6 @@ class _AdvancePaymentScreenState extends State ? DateFormat('dd MMM yyyy').format(parsedDate) : (dateStr.isNotEmpty ? dateStr : '—'); - final formattedTime = - parsedDate != null ? DateFormat('hh:mm a').format(parsedDate) : ''; - final project = item.name ?? ''; final desc = item.title ?? ''; final amount = (item.amount ?? 0).toDouble(); @@ -356,16 +353,6 @@ class _AdvancePaymentScreenState extends State style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), - if (formattedTime.isNotEmpty) ...[ - const SizedBox(width: 6), - Text( - formattedTime, - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade500, - fontStyle: FontStyle.italic), - ), - ] ], ), const SizedBox(height: 4), diff --git a/lib/view/layouts/user_profile_right_bar.dart b/lib/view/layouts/user_profile_right_bar.dart index ddca327..9d19155 100644 --- a/lib/view/layouts/user_profile_right_bar.dart +++ b/lib/view/layouts/user_profile_right_bar.dart @@ -247,6 +247,43 @@ class _UserProfileBarState extends State final tenants = tenantSwitchController.tenants; if (tenants.isEmpty) return _noTenantContainer(); + // If only one organization, don't show switch option + if (tenants.length == 1) { + final selectedTenant = tenants.first; + return Container( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300, width: 1), + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Container( + width: 20, + height: 20, + color: Colors.grey.shade200, + child: TenantLogo(logoImage: selectedTenant.logoImage), + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + selectedTenant.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.bold, + color: contentTheme.primary), + ), + ), + const Icon(Icons.check_circle, color: Colors.green, size: 18), + ], + ), + ); + } final selectedTenant = TenantService.currentTenant; diff --git a/lib/view/service_project/service_project_job_detail_screen.dart b/lib/view/service_project/service_project_job_detail_screen.dart index 814e68d..4805ded 100644 --- a/lib/view/service_project/service_project_job_detail_screen.dart +++ b/lib/view/service_project/service_project_job_detail_screen.dart @@ -47,11 +47,13 @@ class _JobDetailsScreenState extends State with UIMixin { @override void initState() { super.initState(); - controller = Get.put(ServiceProjectDetailsController()); + controller = Get.find(); + // fetch and seed local selected lists controller.fetchJobDetail(widget.jobId).then((_) { final job = controller.jobDetail.value?.data; if (job != null) { - _selectedTags.value = job.tags ?? []; + _selectedTags.value = + (job.tags ?? []).map((t) => Tag(id: t.id, name: t.name)).toList(); _titleController.text = job.title ?? ''; _descriptionController.text = job.description ?? ''; _startDateController.text = DateTimeUtils.convertUtcToLocal( @@ -61,7 +63,6 @@ class _JobDetailsScreenState extends State with UIMixin { job.dueDate ?? '', format: "yyyy-MM-dd"); _selectedAssignees.value = job.assignees ?? []; - _selectedTags.value = job.tags ?? []; } }); } @@ -76,7 +77,18 @@ class _JobDetailsScreenState extends State with UIMixin { super.dispose(); } + bool _tagsAreDifferent(List original, List current) { + // Compare by id / name sets (simple equality) + final origIds = original.map((t) => t.id ?? '').toSet(); + final currIds = current.map((t) => t.id ?? '').toSet(); + final origNames = original.map((t) => t.name?.trim() ?? '').toSet(); + final currNames = current.map((t) => t.name?.trim() ?? '').toSet(); + + return !(origIds == currIds && origNames == currNames); + } + Future _editJob() async { + _processTagsInput(); final job = controller.jobDetail.value?.data; if (job == null) return; @@ -116,39 +128,56 @@ class _JobDetailsScreenState extends State with UIMixin { }); } - final originalAssignees = job.assignees; - final assigneesPayload = originalAssignees?.map((a) { + // Assignees payload (keep same approach) + final originalAssignees = job.assignees ?? []; + final assigneesPayload = originalAssignees.map((a) { final isSelected = _selectedAssignees.any((s) => s.id == a.id); return {"employeeId": a.id, "isActive": isSelected}; }).toList(); + // add newly added assignees for (var s in _selectedAssignees) { - if (!(originalAssignees?.any((a) => a.id == s.id) ?? false)) { - assigneesPayload?.add({"employeeId": s.id, "isActive": true}); + if (!(originalAssignees.any((a) => a.id == s.id))) { + assigneesPayload.add({"employeeId": s.id, "isActive": true}); } } operations.add( {"op": "replace", "path": "/assignees", "value": assigneesPayload}); - final originalTags = job.tags; - final replaceTagsPayload = originalTags?.map((t) { - final isSelected = _selectedTags.any((s) => s.id == t.id); - return {"id": t.id, "name": t.name, "isActive": isSelected}; - }).toList(); + // TAGS: build robust payload using original tags and current selection + final originalTags = job.tags ?? []; + final currentTags = _selectedTags.toList(); - final addTagsPayload = _selectedTags - .where((t) => t.id == "0") - .map((t) => {"name": t.name, "isActive": true}) - .toList(); + // Only add tags operation if something changed + if (_tagsAreDifferent(originalTags, currentTags)) { + final List> finalTagsPayload = []; - if ((replaceTagsPayload?.isNotEmpty ?? false)) { - operations - .add({"op": "replace", "path": "/tags", "value": replaceTagsPayload}); - } + // 1) For existing original tags - we need to mark isActive true/false depending on whether they're in currentTags + for (var ot in originalTags) { + final isSelected = currentTags.any((ct) => + (ct.id != null && ct.id == ot.id) || + (ct.name?.trim() == ot.name?.trim())); + finalTagsPayload.add({ + "id": ot.id, + "name": ot.name, + "isActive": isSelected, + }); + } - if (addTagsPayload.isNotEmpty) { - operations.add({"op": "add", "path": "/tags", "value": addTagsPayload}); + // 2) Add newly created tags from currentTags that don't have a valid id (id == "0" or null) + for (var ct in currentTags.where((c) => c.id == null || c.id == "0")) { + finalTagsPayload.add({ + "name": ct.name, + "isActive": true, + }); + } + + operations.add({ + "op": "replace", + "path": "/tags", + "value": finalTagsPayload, + }); } if (operations.isEmpty) { @@ -169,10 +198,18 @@ class _JobDetailsScreenState extends State with UIMixin { title: "Success", message: "Job updated successfully", type: SnackbarType.success); + + // re-fetch job detail and update local selected tags from server response await controller.fetchJobDetail(widget.jobId); final updatedJob = controller.jobDetail.value?.data; + if (updatedJob != null) { - _selectedTags.value = updatedJob.tags ?? []; + _selectedTags.value = (updatedJob.tags ?? []) + .map((t) => Tag(id: t.id, name: t.name)) + .toList(); + + // UI refresh to reflect tags instantly + setState(() {}); } isEditing.value = false; @@ -184,6 +221,29 @@ class _JobDetailsScreenState extends State with UIMixin { } } + void _processTagsInput() { + final input = _tagTextController.text; + + // Remove comma behaviour → treat whole input as one tag + String tag = input.trim(); + if (tag.isEmpty) { + _tagTextController.clear(); + return; + } + + // Convert underscore to space + tag = tag.replaceAll("_", " "); + + // Avoid duplicate tags (case-insensitive) + if (!_selectedTags + .any((t) => (t.name ?? "").toLowerCase() == tag.toLowerCase())) { + _selectedTags.add(Tag(id: "0", name: tag)); + } + + // Clear text field + _tagTextController.clear(); + } + Future _handleTagAction() async { final job = controller.jobDetail.value?.data; if (job == null) return; @@ -408,10 +468,8 @@ class _JobDetailsScreenState extends State with UIMixin { border: Border.all(color: Colors.grey.shade400), ), alignment: Alignment.centerLeft, - child: Text( - "Tap to select assignees", - style: TextStyle(fontSize: 14, color: Colors.grey[700]), - ), + child: Text("Tap to select assignees", + style: TextStyle(fontSize: 14, color: Colors.grey[700])), ), ), ], @@ -422,19 +480,24 @@ class _JobDetailsScreenState extends State with UIMixin { Widget _tagEditor() { return Obx(() { final editing = isEditing.value; - final tags = _selectedTags; + final job = controller.jobDetail.value?.data; + + final displayTags = editing ? _selectedTags : (job?.tags ?? []); + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Wrap( spacing: 6, - children: tags + children: displayTags .map( (t) => Chip( label: Text(t.name ?? ''), onDeleted: editing ? () { - _selectedTags.remove(t); + _selectedTags.removeWhere((x) => + (x.id != null && x.id == t.id) || + (x.name == t.name)); } : null, ), @@ -445,17 +508,21 @@ class _JobDetailsScreenState extends State with UIMixin { if (editing) TextField( controller: _tagTextController, - onSubmitted: (v) { - final value = v.trim(); - if (value.isNotEmpty && !tags.any((t) => t.name == value)) { - _selectedTags.add(Tag(id: "0", name: value)); + onChanged: (value) { + // If space or comma typed → process tags immediately + if (value.endsWith(" ") || value.contains(",")) { + _processTagsInput(); } - _tagTextController.clear(); + }, + onSubmitted: (_) { + // Still supports ENTER + _processTagsInput(); }, decoration: InputDecoration( - hintText: "Type and press enter to add tags", - border: - OutlineInputBorder(borderRadius: BorderRadius.circular(5)), + hintText: "Type tags (space or comma to add multiple tags)", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + ), isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), @@ -495,11 +562,10 @@ class _JobDetailsScreenState extends State with UIMixin { const Spacer(), Obx(() => IconButton( icon: Icon( - isAttendanceExpanded.value - ? Icons.expand_less - : Icons.expand_more, - color: Colors.grey[600], - ), + isAttendanceExpanded.value + ? Icons.expand_less + : Icons.expand_more, + color: Colors.grey[600]), onPressed: () async { isAttendanceExpanded.value = !isAttendanceExpanded.value; @@ -526,22 +592,17 @@ class _JobDetailsScreenState extends State with UIMixin { height: 16, width: 16, child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, - ), - ) + color: Colors.white, strokeWidth: 2)) : Icon(action == 0 ? Icons.login : Icons.logout), label: MyText.bodyMedium( - action == 0 ? "Tag In" : "Tag Out", - fontWeight: 600, - color: Colors.white, - ), + action == 0 ? "Tag In" : "Tag Out", + fontWeight: 600, + color: Colors.white), onPressed: isLoading ? null : _handleTagAction, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 20), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), + borderRadius: BorderRadius.circular(5)), backgroundColor: action == 0 ? Colors.green : Colors.red, ), @@ -563,10 +624,8 @@ class _JobDetailsScreenState extends State with UIMixin { if (logs.isEmpty) { return Padding( padding: const EdgeInsets.only(top: 12), - child: MyText.bodyMedium( - "No attendance logs available", - color: Colors.grey[600], - ), + child: MyText.bodyMedium("No attendance logs available", + color: Colors.grey[600]), ); } @@ -601,25 +660,21 @@ class _JobDetailsScreenState extends State with UIMixin { Row( children: [ Icon( - log.action == 0 ? Icons.login : Icons.logout, - color: log.action == 0 - ? Colors.green - : Colors.red, - size: 18, - ), + log.action == 0 + ? Icons.login + : Icons.logout, + color: log.action == 0 + ? Colors.green + : Colors.red, + size: 18), const SizedBox(width: 6), Expanded( - child: Text( - employeeName, - style: const TextStyle( - fontWeight: FontWeight.w600), - ), - ), - Text( - "$date | $time", - style: TextStyle( - fontSize: 12, color: Colors.grey[700]), - ), + child: Text(employeeName, + style: const TextStyle( + fontWeight: FontWeight.w600))), + Text("$date | $time", + style: TextStyle( + fontSize: 12, color: Colors.grey[700])), ], ), const SizedBox(height: 4), @@ -627,12 +682,9 @@ class _JobDetailsScreenState extends State with UIMixin { // Comment if (log.comment?.isNotEmpty == true) Padding( - padding: const EdgeInsets.only(top: 4), - child: Text( - log.comment!, - style: const TextStyle(fontSize: 13), - ), - ), + padding: const EdgeInsets.only(top: 4), + child: Text(log.comment!, + style: const TextStyle(fontSize: 13))), // Location if (log.latitude != null && log.longitude != null) @@ -657,14 +709,12 @@ class _JobDetailsScreenState extends State with UIMixin { Icon(Icons.location_on, size: 14, color: Colors.blue), SizedBox(width: 4), - Text( - "View Location", - style: TextStyle( - fontSize: 12, - color: Colors.blue, - decoration: - TextDecoration.underline), - ), + Text("View Location", + style: TextStyle( + fontSize: 12, + color: Colors.blue, + decoration: + TextDecoration.underline)), ], ), ), @@ -679,16 +729,13 @@ class _JobDetailsScreenState extends State with UIMixin { context: context, builder: (_) => Dialog( child: Image.network( - log.document!.preSignedUrl, - fit: BoxFit.cover, - height: 250, - errorBuilder: (_, __, ___) => - const Icon( - Icons.broken_image, - size: 50, - color: Colors.grey, - ), - ), + log.document!.preSignedUrl, + fit: BoxFit.cover, + height: 250, + errorBuilder: (_, __, ___) => + const Icon(Icons.broken_image, + size: 50, + color: Colors.grey)), ), ), child: ClipRRect( @@ -701,10 +748,9 @@ class _JobDetailsScreenState extends State with UIMixin { width: 50, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon( - Icons.broken_image, - size: 40, - color: Colors.grey, - ), + Icons.broken_image, + size: 40, + color: Colors.grey), ), ), ), @@ -728,14 +774,10 @@ class _JobDetailsScreenState extends State with UIMixin { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - flex: 3, - child: MyText.bodySmall(label, - fontWeight: 600, color: Colors.grey.shade700), - ), - Expanded( - flex: 5, - child: MyText.bodyMedium(value, fontWeight: 500), - ), + flex: 3, + child: MyText.bodySmall(label, + fontWeight: 600, color: Colors.grey.shade700)), + Expanded(flex: 5, child: MyText.bodyMedium(value, fontWeight: 500)), ], ); } @@ -912,12 +954,11 @@ class JobTimeline extends StatelessWidget { isFirst: index == 0, isLast: index == reversedLogs.length - 1, indicatorStyle: const IndicatorStyle( - width: 16, - height: 16, - indicator: DecoratedBox( - decoration: - BoxDecoration(color: Colors.blue, shape: BoxShape.circle)), - ), + width: 16, + height: 16, + indicator: DecoratedBox( + decoration: BoxDecoration( + color: Colors.blue, shape: BoxShape.circle))), beforeLineStyle: LineStyle(color: Colors.grey.shade300, thickness: 2), endChild: Padding( padding: const EdgeInsets.all(12), @@ -932,13 +973,12 @@ class JobTimeline extends StatelessWidget { const SizedBox(height: 10), Row(children: [ Container( - padding: - const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4)), - child: MyText.bodySmall(initials, fontWeight: 600), - ), + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(4)), + child: MyText.bodySmall(initials, fontWeight: 600)), const SizedBox(width: 6), Expanded(child: MyText.bodySmall(updatedBy)), ]),