import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:nhance_partner/presentation/screens/Enquiry/policy_claims_endros/add_claim_stepper_dialog.dart'; import 'package:nhance_partner/presentation/providers/userRoleProvider.dart'; import '../../../../core/services/api_service.dart'; import '../../../../data/utils/Pagination.dart'; import '../../../../data/utils/toastNotification.dart'; import '../../../layouts/main_layout.dart'; import '../../../layouts/responsive_layout.dart'; import '../../../providers/manager_provider.dart'; import '../../../themes/indicators/date_range_picker_field.dart'; import '../../../themes/indicators/export_btn.dart'; import '../../../themes/indicators/search_field_theme.dart'; class claimList extends ConsumerStatefulWidget { const claimList({super.key}); @override ConsumerState createState() => claimListState(); } class claimListState extends ConsumerState { static const Color _teal = Color(0xFF14A492); static const Color _surfaceGrey = Color(0xFFF1F5F9); static const Color _border = Color(0xFFE2E8F0); static const Color _muted = Color(0xFF64748B); static const Color _filterFocusTeal = Color(0xFF50A398); static const Color _actionDownloadBlue = Color(0xFF1565C0); int currentPage = 1; int itemsPerPage = 10; late ApiService apiService; List> getStaffData = []; List> originalData = []; List> filteredData = []; bool isLoading = false; final TextEditingController _quickSearchController = TextEditingController(); final TextEditingController _startDateController = TextEditingController(); final TextEditingController _endDateController = TextEditingController(); final ScrollController _tableVScrollController = ScrollController(); dynamic userId; dynamic roleId; dynamic managerId; // bool _pendingTab = true; List> _masterClaimTypeMaps = []; List> _masterInsurerMaps = []; List> _masterClaimStatusMaps = []; String? _filterClaimType; String? _filterInsurer; /// Selected claim status master id (stored in filter choice `v`). String? _filterStatus; @override void initState() { super.initState(); apiService = ApiService(); Future.microtask(() { managerId = ref.read(managerIdProvider); roleId = ref.read(userRoleProvider); userId = ref.read(userIdProvider); if (userId != null) { loadClaimsList(); } _loadFilterMasters(); }); } Future _loadFilterMasters() async { try { final claimR = await apiService.fetchMasterDropDown('Claim'); if ((claimR['status'] == 'success' || claimR['status'] == 200) && claimR['data'] is List) { if (mounted) { setState(() { _masterClaimTypeMaps = List>.from(claimR['data']); }); } } final insR = await apiService.fetchMasterDropDown('Insurers'); if ((insR['status'] == 'success' || insR['status'] == 200) && insR['data'] is List) { if (mounted) { setState(() { _masterInsurerMaps = List>.from(insR['data']); }); } } final statusR = await apiService.fetchClaimStatusList(); if ((statusR['status'] == 'success' || statusR['status'] == 200) && statusR['data'] is List && (statusR['data'] as List).isNotEmpty) { if (mounted) { setState(() { _masterClaimStatusMaps = List>.from(statusR['data']); }); } } } catch (_) {} } @override void dispose() { _quickSearchController.dispose(); _startDateController.dispose(); _endDateController.dispose(); _tableVScrollController.dispose(); super.dispose(); } String? _apiDateFromPicker(String text) { final t = text.trim(); if (t.isEmpty) return null; try { final d = DateRangePickerField.storageFormat.parse(t); return DateFormat('yyyy-MM-dd').format(d); } catch (_) { return null; } } int? _claimTypeIdForFilter() { if (_filterClaimType == null || _filterClaimType!.trim().isEmpty) { return null; } return int.tryParse(_filterClaimType!.trim()); } int? _insurerIdForFilter() { if (_filterInsurer == null || _filterInsurer!.trim().isEmpty) return null; return int.tryParse(_filterInsurer!.trim()); } int? _claimStatusIdForFilter() { if (_filterStatus == null || _filterStatus!.trim().isEmpty) return null; return int.tryParse(_filterStatus!.trim()); } Future loadClaimsList() async { if (!mounted) return; setState(() => isLoading = true); try { final role = roleId?.toString().toLowerCase() ?? ''; final mid = int.tryParse(managerId?.toString() ?? ''); final uid = int.tryParse(userId?.toString() ?? ''); final response = await apiService.fetchClaims( managerId: (role == 'manager' || role == 'accounts') ? mid : null, agentId: role == 'agent' ? uid : null, handlerId: role == 'handler' ? uid : null, staffId: (role != 'manager' && role != 'accounts' && role != 'agent' && role != 'handler') ? uid : null, claimTypeId: _claimTypeIdForFilter(), claimStatusId: _claimStatusIdForFilter(), insurerId: _insurerIdForFilter(), fromDate: _apiDateFromPicker(_startDateController.text), toDate: _apiDateFromPicker(_endDateController.text), ); if (!mounted) return; if (response['status'] == 'success' || response['status'] == 200 || response['status'] == '200') { final data = response['data']; setState(() { if (data is List) { getStaffData = data .whereType() .map((e) => Map.from(e)) .toList(); } else if (data is Map) { getStaffData = [Map.from(data)]; } else { getStaffData = []; } originalData = List>.from(getStaffData); _rebuildFiltered(); }); } else { setState(() { getStaffData = []; originalData = []; filteredData = []; }); } } catch (e) { debugPrint('Claims list error: $e'); if (mounted) { setState(() { getStaffData = []; originalData = []; filteredData = []; }); } } finally { if (mounted) setState(() => isLoading = false); } } static String _pickStr(Map row, List keys) { for (final k in keys) { final v = row[k]; if (v != null && v.toString().trim().isNotEmpty) { return v.toString(); } } return '-'; } // bool _isSettled(Map item) { // final raw = _pickStr(item, ['claim_status_id']); // final id = int.tryParse(raw); // return id == 65; // } // bool _matchesTab(Map item) { // return _pendingTab ? !_isSettled(item) : _isSettled(item); // } DateTime? _parseCreated(Map item) { final raw = _pickStr(item, [ 'created_at', 'created_date', 'created_on', 'inserted_at', ]); if (raw == '-') return null; try { return DateTime.parse(raw); } catch (_) { return null; } } void _addSearchText(List out, String? value) { if (value == null) return; final s = value.trim(); if (s.isEmpty || s == '-') return; out.add(s); } void _addSearchDate(List out, Map item, List keys) { final raw = _pickStr(item, keys); if (raw == '-') return; _addSearchText(out, raw); _addSearchText(out, _formatDate(raw)); } List _searchableTexts(Map item) { final texts = []; for (final value in item.values) { _addSearchText(texts, value?.toString()); } _addSearchText(texts, _pickStr(item, ['policy_no', 'policy_number'])); _addSearchText(texts, _pickStr(item, ['claim_number'])); _addSearchText(texts, _pickStr(item, ['broker_name', 'broker'])); _addSearchText( texts, _pickStr(item, ['agent_name', 'partner_name', 'partner']), ); _addSearchDate(texts, item, [ 'date_of_incident', 'accident_date', 'accident_datetime', 'date_of_accident', ]); _addSearchDate(texts, item, [ 'created_at', 'created_date', 'created_on', 'inserted_at', ]); return texts; } bool _matchesQuickSearch(Map item, String query) { if (query.isEmpty) return true; final q = query.toLowerCase(); return _searchableTexts(item).any((t) => t.toLowerCase().contains(q)); } void _rebuildFiltered() { final q = _quickSearchController.text.trim(); filteredData = originalData.where((item) { return _matchesQuickSearch(item, q); }).toList(); } void _applyFilterBar() { setState(() => currentPage = 1); loadClaimsList(); } void _onQuickSearch(String _) { setState(() { _rebuildFiltered(); currentPage = 1; }); } // void _setTab(bool pending) { // if (_pendingTab == pending) return; // setState(() { // _pendingTab = pending; // _rebuildFiltered(); // currentPage = 1; // }); // } // int get _pendingCount => // originalData.where((e) => !_isSettled(e)).length; // int get _settledCount => originalData.where(_isSettled).length; bool get _canShowDeleteAction { final role = roleId?.toString().trim().toLowerCase() ?? ''; return role == 'manager' || role == 'accounts' || role == 'account'; } void _confirmDelete(BuildContext context, String id) { showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.red.shade600, size: 22), const SizedBox(width: 8), Text( 'Delete Claim', style: GoogleFonts.poppins(fontSize: 15, fontWeight: FontWeight.w600), ), ], ), content: Text( 'Are you sure you want to delete this claim?\nThis action cannot be undone.', style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade700), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text( 'Cancel', style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade600), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade600, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), onPressed: () async { Navigator.pop(ctx); await _deleteClaim(id); }, child: Text( 'Delete', style: GoogleFonts.poppins(fontSize: 13, color: Colors.white), ), ), ], ), ); } Future _deleteClaim(String id) async { if (id.isEmpty || id == '-') { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Invalid claim id.', style: GoogleFonts.poppins(fontSize: 13), ), backgroundColor: Colors.red.shade600, behavior: SnackBarBehavior.floating, ), ); return; } final updatedBy = userId?.toString().trim(); if (updatedBy == null || updatedBy.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'User id not available.', style: GoogleFonts.poppins(fontSize: 13), ), backgroundColor: Colors.red.shade600, behavior: SnackBarBehavior.floating, ), ); return; } try { setState(() => isLoading = true); final response = await apiService.deleteClaim( id: id, updatedBy: updatedBy, ); if (response['status'] == 'success' || response['code'] == 200) { refresh(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Claim deleted successfully.', style: GoogleFonts.poppins(fontSize: 13), ), backgroundColor: Colors.green.shade600, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), duration: const Duration(seconds: 2), ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( response['message']?.toString() ?? 'Failed to delete claim.', style: GoogleFonts.poppins(fontSize: 13), ), backgroundColor: Colors.red.shade600, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), duration: const Duration(seconds: 2), ), ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: $e', style: GoogleFonts.poppins(fontSize: 13)), backgroundColor: Colors.red.shade600, behavior: SnackBarBehavior.floating, ), ); } finally { if (mounted) setState(() => isLoading = false); } } List _uniqueFrom(String Function(Map) pick) { final s = {}; for (final r in originalData) { final v = pick(r); if (v != '-') s.add(v); } return s.toList()..sort(); } List> get _paginatedSorted { final sorted = [...filteredData] ..sort((a, b) { final ida = int.tryParse(_pickStr(a, ['id'])) ?? 0; final idb = int.tryParse(_pickStr(b, ['id'])) ?? 0; return idb.compareTo(ida); }); if (sorted.isEmpty) return []; final maxPage = (sorted.length / itemsPerPage).ceil(); final safePage = currentPage.clamp(1, maxPage); final start = (safePage - 1) * itemsPerPage; final end = (start + itemsPerPage).clamp(0, sorted.length); return sorted.sublist(start, end); } DateTime? _parseClaimDateTime(String raw) { final t = raw.trim(); if (t.isEmpty || t == '-') return null; final patterns = [ DateFormat('dd-MM-yyyy hh:mm a'), DateFormat('dd-MM-yyyy HH:mm'), DateFormat('dd-MM-yyyy'), DateFormat('yyyy-MM-dd HH:mm:ss'), DateFormat('yyyy-MM-dd'), ]; for (final p in patterns) { try { return p.parse(t); } catch (_) {} } try { return DateTime.parse(t); } catch (_) { return null; } } String _formatDate(String rawDate) { final dt = _parseClaimDateTime(rawDate); if (dt != null) return DateFormat('dd-MM-yyyy').format(dt); return rawDate; } String _formatDateTime(String raw) { final dt = _parseClaimDateTime(raw); if (dt != null) { return DateFormat('dd-MM-yyyy, hh:mm a').format(dt); } return raw; } void refresh() { loadClaimsList(); } void _clearFilters() { setState(() { _startDateController.clear(); _endDateController.clear(); _filterClaimType = null; _filterInsurer = null; _filterStatus = null; _quickSearchController.clear(); currentPage = 1; }); loadClaimsList(); } InputDecoration _filterDropdownDecoration(String hintText) { return InputDecoration( isDense: true, hintText: hintText, hintStyle: GoogleFonts.inter(fontSize: 11, color: const Color(0xFF94A3B8)), filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _filterFocusTeal, width: 1.5), ), ); } Map _filterAllItem(String label) => { 'v': '', 'label': label, 'sub': '', }; Map _findSelectedChoice( List> choices, String? value, ) { final v = value ?? ''; for (final c in choices) { if ((c['v']?.toString() ?? '') == v) return c; } return choices.isNotEmpty ? choices.first : _filterAllItem(''); } List> _claimTypeFilterChoices(List fromData) { final byId = >{}; for (final m in _masterClaimTypeMaps) { final id = (m['id'] ?? '').toString().trim(); final label = (m['claim_type'] ?? '').toString().trim(); if (id.isEmpty || label.isEmpty) continue; byId[id] = {'v': id, 'label': label, 'sub': ''}; } for (final label in fromData) { if (label.isEmpty) continue; if (byId.values.any((c) => (c['label'] as String) == label)) continue; for (final row in originalData) { if (_pickStr(row, ['claim_type_value']) == label) { final id = (row['claim_type_id'] ?? '').toString().trim(); if (id.isNotEmpty) byId[id] = {'v': id, 'label': label, 'sub': ''}; break; } } } final list = byId.values.toList() ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); return [_filterAllItem('Claim type'), ...list]; } List> _statusFilterChoices(List fromData) { final byId = >{}; void addStatus(String? id, String label) { final idStr = id?.toString().trim() ?? ''; final lab = label.trim(); if (idStr.isEmpty || lab.isEmpty || lab == '-') return; byId[idStr] = {'v': idStr, 'label': lab, 'sub': ''}; } for (final m in _masterClaimStatusMaps) { addStatus( m['id']?.toString(), (m['claim_status'] ?? m['status'] ?? m['name'] ?? m['claim_status_value'] ?? m['status_name'] ?? '') .toString(), ); } for (final row in originalData) { addStatus( row['claim_status_id']?.toString(), _pickStr(row, ['claim_status_value']), ); } for (final label in fromData) { if (label.isEmpty) continue; if (byId.values.any((c) => (c['label'] as String) == label)) continue; for (final row in originalData) { if (_pickStr(row, ['claim_status_value']) == label) { addStatus(row['claim_status_id']?.toString(), label); break; } } } final list = byId.values.toList() ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); return [_filterAllItem('Status'), ...list]; } List> _insurerFilterChoices(List fromData) { final byId = >{}; for (final m in _masterInsurerMaps) { final id = (m['id'] ?? '').toString().trim(); final label = (m['insurer_short_name'] ?? m['insurer_name'] ?? m['name'] ?? '') .toString() .trim(); if (id.isEmpty || label.isEmpty) continue; byId[id] = {'v': id, 'label': label, 'sub': ''}; } for (final label in fromData) { if (label.isEmpty) continue; if (byId.values.any((c) => (c['label'] as String) == label)) continue; for (final row in originalData) { if (_pickStr(row, ['insurer_short_name']) == label) { final id = (row['insurer_id'] ?? '').toString().trim(); if (id.isNotEmpty) byId[id] = {'v': id, 'label': label, 'sub': ''}; break; } } } final list = byId.values.toList() ..sort((a, b) => (a['label'] as String).compareTo(b['label'] as String)); return [_filterAllItem('Select Insurer'), ...list]; } InputDecoration _popupSearchDecoration(String hint) { return InputDecoration( filled: true, fillColor: Colors.white, hintText: hint, hintStyle: GoogleFonts.inter(fontSize: 12, color: const Color(0xFF94A3B8)), isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: _filterFocusTeal, width: 1.5), ), ); } Widget _buildFilterDropdownSearch({ required double width, String? placeholder, required String? selectedValue, required List> choices, required void Function(String?) onChanged, bool twoLineItems = false, }) { final hint = placeholder ?? (choices.isNotEmpty ? (choices.first['label'] ?? '').toString() : ''); final selected = _findSelectedChoice(choices, selectedValue); return SizedBox( height: 38, width: width, child: DropdownSearch>( selectedItem: selected, items: (filter, infiniteScrollProps) => choices, itemAsString: (m) { final label = (m['label'] ?? '').toString(); final sub = (m['sub'] ?? '').toString(); if (twoLineItems && sub.isNotEmpty) return '$label $sub'; return label; }, compareFn: (a, b) => (a['v']?.toString() ?? '') == (b['v']?.toString() ?? ''), decoratorProps: DropDownDecoratorProps( decoration: _filterDropdownDecoration(hint), ), dropdownBuilder: (context, selectedItem) { final m = selectedItem ?? _filterAllItem(hint); final isAll = (m['v']?.toString() ?? '').isEmpty; final label = (m['label'] ?? hint).toString(); return Align( alignment: Alignment.centerLeft, child: Text( label, style: GoogleFonts.inter( fontSize: 11, color: isAll ? const Color(0xFF94A3B8) : const Color(0xFF334155), ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ); }, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: const BoxConstraints(maxHeight: 280), menuProps: MenuProps( backgroundColor: Colors.white, elevation: 8, borderRadius: BorderRadius.circular(8), ), searchFieldProps: TextFieldProps( decoration: _popupSearchDecoration('Search'), ), itemBuilder: (context, item, isDisabled, isSelected) { final isAll = (item['v']?.toString() ?? '').isEmpty; if (isAll) return const SizedBox.shrink(); final label = (item['label'] ?? '').toString(); final sub = (item['sub'] ?? '').toString(); final showTwoLine = twoLineItems && !isAll && sub.isNotEmpty; return Container( color: isSelected ? const Color(0xFFF1F5F9) : null, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: showTwoLine ? Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( label.toUpperCase(), style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w700, color: const Color(0xFF0F172A), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), if (sub.isNotEmpty) ...[ const SizedBox(height: 2), Text( sub, style: GoogleFonts.inter( fontSize: 10, color: const Color(0xFF94A3B8), ), ), ], ], ) : Text( label, style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: const Color(0xFF0F172A), ), ), ), ); }, ), onChanged: (val) { final v = val?['v']?.toString() ?? ''; onChanged(v.isEmpty ? null : v); }, ), ); } Map get _exportConfig => { 'displayHeaders': const [ 'S No', 'Created Date', 'Claim Type', 'Vehicle No', 'Insured Name', 'Policy Number', 'Claim Number', 'Accident Date', 'Insurer', 'Broker', 'Partner', 'Remarks', 'Status', 'Pending Days', ], 'keys': const [ 'sno', 'created_at', 'claim_type_value', 'reg_no', 'insured_name', 'policy_no', 'claim_number', 'accident_date', 'insurer_short_name', 'broker', 'partner', 'remark', 'claim_status_value', 'pending_days', ], 'valueFormatters': { 'created_at': (dynamic v, Map row) { final raw = _pickStr(row, [ 'created_at', 'created_date', 'created_on', ]); if (raw == '-') return '-'; return _formatDate(raw); }, 'accident_date': (dynamic v, Map row) { final raw = _pickStr(row, [ 'date_of_incident', 'accident_date', 'accident_datetime', 'date_of_accident', ]); if (raw == '-') return '-'; return _formatDate(raw); }, 'insured_name': (dynamic v, Map row) => _pickStr(row, [ 'insured_name', 'customer_name', 'proposer_name', ]), 'claim_number': (dynamic v, Map row) => _pickStr(row, ['claim_number']), 'broker': (dynamic v, Map row) => _pickStr(row, ['broker_name', 'broker']), 'partner': (dynamic v, Map row) => _pickStr(row, ['agent_name', 'partner_name', 'partner']), 'remark': (dynamic v, Map row) => _pickStr(row, ['remark']), 'pending_days': (dynamic v, Map row) => _pendingDaysLabel(row), 'settled_amount': (dynamic v, Map row) => _pickStr(row, [ 'settled_amount', 'claim_amount', 'settlement_amount', ]), }, }; Future _onExportClaims() async { final cfg = _exportConfig; try { await ExcelExporter.exportToExcel( sheetName: 'Claims', data: filteredData, displayHeaders: cfg['displayHeaders'] as List, keys: cfg['keys'] as List, fileName: 'claim_list', valueFormatters: cfg['valueFormatters'] as Map?, ); if (mounted) { ToastHelper.showSuccessToast(context, 'Export finished'); } } catch (e) { if (mounted) { ToastHelper.showErrorToast(context, 'Export failed: $e'); } } } @override Widget build(BuildContext context) { return MainLayout( title: 'Claims', body: SelectionArea( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildHeaderTabs(context), const SizedBox(height: 14), _buildFilterBar(context), const SizedBox(height: 12), Expanded( child: isLoading ? const Center(child: CircularProgressIndicator()) : ResponsiveLayout.isMobile(context) ? _buildMobileList() : _buildDesktopTable(), ), const SizedBox(height: 8), _buildFooter(context), ], ), ), ), ); } Widget _buildHeaderTabs(BuildContext context) { return Text( 'Claims', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, ), ); } // Widget _tabSwitcher() { // return Container( // padding: const EdgeInsets.all(4), // decoration: BoxDecoration( // color: const Color(0xFFF1F5F9), // borderRadius: BorderRadius.circular(10), // ), // child: Row( // mainAxisSize: MainAxisSize.min, // children: [ // _tabChip( // label: 'Pending Claims', // count: _pendingCount, // selected: _pendingTab, // onTap: () => _setTab(true), // ), // const SizedBox(width: 6), // _tabChip( // label: 'Settled Claims', // count: _settledCount, // selected: !_pendingTab, // onTap: () => _setTab(false), // ), // ], // ), // ); // } // Widget _tabChip({ // required String label, // required int count, // required bool selected, // required VoidCallback onTap, // }) { // return Material( // color: Colors.transparent, // child: InkWell( // onTap: onTap, // borderRadius: BorderRadius.circular(8), // child: AnimatedContainer( // duration: const Duration(milliseconds: 180), // padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), // decoration: BoxDecoration( // color: selected ? Colors.white : Colors.transparent, // borderRadius: BorderRadius.circular(8), // boxShadow: selected // ? [ // BoxShadow( // color: Colors.black.withValues(alpha: 0.06), // blurRadius: 8, // offset: const Offset(0, 2), // ), // ] // : null, // ), // child: Row( // mainAxisSize: MainAxisSize.min, // children: [ // Text( // label, // style: GoogleFonts.inter( // fontSize: 12, // fontWeight: FontWeight.w600, // color: selected ? _teal : _muted, // ), // ), // const SizedBox(width: 8), // Container( // width: 26, // height: 26, // alignment: Alignment.center, // decoration: BoxDecoration( // color: selected // ? _teal.withValues(alpha: 0.12) // : const Color(0xFFE2E8F0), // shape: BoxShape.circle, // ), // child: Text( // '$count', // style: GoogleFonts.inter( // fontSize: 11, // fontWeight: FontWeight.w700, // color: selected ? _teal : _muted, // ), // ), // ), // ], // ), // ), // ), // ); // } Widget _buildFilterBar(BuildContext context) { final isMobile = ResponsiveLayout.isMobile(context); final inner = isMobile ? Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _filterControlsRow(context), const SizedBox(height: 10), _buildSearchAndActions(context), ], ) : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _filterControlsRow(context)), _buildSearchAndActions(context), ], ); return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), decoration: BoxDecoration( border: Border.all(color: _border), borderRadius: BorderRadius.circular(10), color: Colors.white, ), child: inner, ); } Widget _filterControlsRow(BuildContext context) { final isMobile = ResponsiveLayout.isMobile(context); final claimTypeChoices = _claimTypeFilterChoices( _uniqueFrom((r) => _pickStr(r, ['claim_type_value'])), ); final insurerChoices = _insurerFilterChoices( _uniqueFrom((r) => _pickStr(r, ['insurer_short_name'])), ); final statusChoices = _statusFilterChoices( _uniqueFrom((r) => _pickStr(r, ['claim_status_value'])), ); final dateW = isMobile ? 168.0 : 175.0; final dropW = isMobile ? 128.0 : 140.0; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ DateRangePickerField( startController: _startDateController, endController: _endDateController, hintText: 'Select date range', showLabelAboveField: false, txtwidth: dateW, txtheight: 38, lastDate: DateTime.now(), useFormField: false, ), const SizedBox(width: 8), _buildFilterDropdownSearch( width: dropW, selectedValue: _filterClaimType, choices: claimTypeChoices, twoLineItems: true, onChanged: (v) => setState(() => _filterClaimType = v), ), const SizedBox(width: 8), _buildFilterDropdownSearch( width: dropW + 8, selectedValue: _filterInsurer, choices: insurerChoices, twoLineItems: true, onChanged: (v) => setState(() => _filterInsurer = v), ), const SizedBox(width: 8), _buildFilterDropdownSearch( width: dropW, selectedValue: _filterStatus, choices: statusChoices, onChanged: (v) => setState(() => _filterStatus = v), ), const SizedBox(width: 8), _squareFilterButton( icon: Icons.search, filled: true, onTap: _applyFilterBar, ), const SizedBox(width: 8), _squareFilterButton( icon: Icons.refresh, filled: false, onTap: _clearFilters, ), ], ), ); } Widget _squareFilterButton({ required VoidCallback onTap, required bool filled, IconData? icon, String? assetPath, String? tooltip, }) { assert(icon != null || assetPath != null); final child = assetPath != null ? Image.asset( assetPath, height: 16, width: 16, color: filled ? Colors.white : _muted, errorBuilder: (_, __, ___) => Icon( Icons.open_in_new_rounded, size: 20, color: filled ? Colors.white : _muted, ), ) : Icon( icon, size: 20, color: filled ? Colors.white : _muted, ); final button = Material( color: filled ? _teal : Colors.white, borderRadius: BorderRadius.circular(8), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( width: 38, height: 38, alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: filled ? null : Border.all(color: _border), ), child: child, ), ), ); if (tooltip != null) { return Tooltip(message: tooltip, child: button); } return button; } Widget _buildSearchAndActions(BuildContext context) { final isMobile = ResponsiveLayout.isMobile(context); final searchField = ThemedSearchField( hintText: 'Search...', backgroundColor: Colors.white, txtHeight: 38, onChanged: _onQuickSearch, controller: _quickSearchController, txtwidth: isMobile ? MediaQuery.sizeOf(context).width * 0.5 : 200, ); final actions = Row( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(width: 10), _squareFilterButton( icon: Icons.add, filled: true, tooltip: 'Add claim', onTap: () { showDialog( context: context, builder: (ctx) => AddClaimStepperDialog( managerId: managerId, userId: userId, role: roleId, onSuccess: refresh, ), ); }, ), const SizedBox(width: 8), _squareFilterButton( filled: true, assetPath: 'assets/miscellaneous/export.png', tooltip: 'Export', onTap: _onExportClaims, ), ], ); if (isMobile) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: searchField), actions, ], ); } return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ searchField, actions, ], ); } Widget _buildFooter(BuildContext context) { final total = filteredData.length; final showing = _paginatedSorted.length; final label = total == 1 ? 'claim' : 'claims'; return Row( children: [ Text( 'Showing $showing of $total $label', style: GoogleFonts.inter(fontSize: 12, color: _muted), ), const Spacer(), Text( total == 0 ? 'Page 1' : 'Page $currentPage', style: GoogleFonts.inter(fontSize: 12, color: _muted), ), const SizedBox(width: 16), PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, totalItems: filteredData.length, onPageChanged: (page) { setState(() => currentPage = page); }, onItemsPerPageChanged: (items) { setState(() { itemsPerPage = items; currentPage = 1; }); }, ), ], ); } int _calculatePendingDays(String? createdAt) { if (createdAt == null || createdAt.isEmpty) return 0; try { final created = DateFormat('dd-MM-yyyy').parse(createdAt.split(' ')[0]); final today = DateTime.now(); return today.difference(created).inDays; } catch (e) { return 0; } } bool _isClosedClaimStatus(Map item) { final status = _pickStr(item, ['claim_status_value', 'status']).toLowerCase(); return status.contains('closed') || status.contains('settled'); } String _pendingDaysLabel(Map item) { final days = _calculatePendingDays(item['created_at']?.toString()); return _isClosedClaimStatus(item) ? '-' : '$days'; } Widget _pendingDaysCell(Map item) { return Expanded( flex: 1, child: Builder( builder: (context) { final days = _calculatePendingDays(item['created_at']?.toString()); return Padding( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 12), child: Center( child: Text( _isClosedClaimStatus(item) ? '-' : '$days', textAlign: TextAlign.center, style: _dataBold.copyWith( color: _isClosedClaimStatus(item) ? _muted : (days > 7 ? Colors.red : days > 3 ? Colors.orange : Colors.green), ), ), ), ); }, ), ); } Widget _buildDesktopTable() { if (filteredData.isEmpty) { return Center( child: Text( 'No claims match your filters.', style: GoogleFonts.inter(color: _muted), ), ); } final rows = _paginatedSorted; final startSerial = (currentPage - 1) * itemsPerPage; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _tableHeaderRow(), Expanded( child: Scrollbar( controller: _tableVScrollController, thumbVisibility: true, interactive: true, child: ListView( controller: _tableVScrollController, primary: false, padding: EdgeInsets.zero, physics: const ClampingScrollPhysics(), children: [ for (var index = 0; index < rows.length; index++) ...[ if (index > 0) const Divider(height: 1, color: _border), _pendingDataRow(rows[index], startSerial + index + 1), ], ], ), ), ), ], ); } Widget _headerCell( String text, { int flex = 1, TextAlign align = TextAlign.start, }) { return Expanded( flex: flex, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), child: Align( alignment: align == TextAlign.center ? Alignment.center : Alignment.centerLeft, child: Text( text.toUpperCase(), softWrap: true, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: align, style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 0.3, color: const Color(0xFF475569), ), ), ), ), ); } Widget _headerCellFixed( String text, { required double width, TextAlign align = TextAlign.start, }) { return SizedBox( width: width, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), child: Align( alignment: align == TextAlign.center ? Alignment.center : Alignment.centerLeft, child: Text( text.toUpperCase(), softWrap: false, maxLines: 1, overflow: TextOverflow.visible, textAlign: align, style: GoogleFonts.inter( fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 0.3, color: const Color(0xFF475569), ), ), ), ), ); } Widget _headerCellSno() { return Expanded( flex: 1, child: Padding( padding: const EdgeInsets.only(left: 6, right: 2, top: 12, bottom: 12), child: Align( alignment: Alignment.centerLeft, child: Text( 'S NO', softWrap: true, maxLines: 2, style: GoogleFonts.inter( fontSize: 9, fontWeight: FontWeight.w700, letterSpacing: 0.2, color: const Color(0xFF475569), ), ), ), ), ); } Widget _tableHeaderRow() { return Container( decoration: BoxDecoration( color: _surfaceGrey, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ _headerCellSno(), _headerCell('Created Date', flex: 2), _headerCell('Claim Type', flex: 2), _headerCell('Vehicle No', flex: 2), _headerCell('Insured Name', flex: 2), _headerCell('Policy Number', flex: 2), _headerCell('Claim Number', flex: 2), _headerCell('Accident Date', flex: 2), _headerCell('Insurer', flex: 2), _headerCell('Broker', flex: 2), _headerCell('Partner', flex: 2), _headerCell('Remarks', flex: 2), _headerCell('Status', flex: 2), _headerCell('Pending Days', flex: 1, align: TextAlign.center), _headerCell('Action', flex: 2, align: TextAlign.center), ], ), ); } Widget _cell(Widget child, {int flex = 1}) { return Expanded( flex: flex, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), child: Align( alignment: Alignment.centerLeft, child: child, ), ), ); } Widget _cellFixed(Widget child, {required double width}) { return SizedBox( width: width, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), child: Align( alignment: Alignment.centerLeft, child: child, ), ), ); } Widget _cellSno(Widget child) { return Expanded( flex: 1, child: Padding( padding: const EdgeInsets.only(left: 6, right: 2, top: 12, bottom: 12), child: Align( alignment: Alignment.centerLeft, child: child, ), ), ); } Widget _cellText( String text, { int flex = 1, int maxLines = 2, FontWeight? fontWeight, bool ellipsis = true, }) { return _cell( Text( text, style: _dataStyle.copyWith( fontWeight: fontWeight ?? FontWeight.w400, ), maxLines: maxLines, softWrap: maxLines > 1, overflow: ellipsis ? TextOverflow.ellipsis : TextOverflow.visible, ), flex: flex, ); } Widget _cellTextFixed( String text, { required double width, int maxLines = 1, FontWeight? fontWeight, TextAlign align = TextAlign.start, }) { return SizedBox( width: width, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 12), child: Align( alignment: align == TextAlign.center ? Alignment.center : Alignment.centerLeft, child: Text( text, textAlign: align, style: _dataStyle.copyWith( fontWeight: fontWeight ?? FontWeight.w400, ), maxLines: maxLines, softWrap: maxLines > 1, overflow: TextOverflow.visible, ), ), ), ); } Widget _cellRemarks(String text, {int flex = 3}) { final display = text.trim().isEmpty ? '-' : text.trim(); final textWidget = Text( display, style: _dataStyle, maxLines: 1, softWrap: false, overflow: TextOverflow.ellipsis, ); if (display == '-') { return _cell(textWidget, flex: flex); } return _cell( Tooltip( richMessage: WidgetSpan( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 420), child: Text( display, style: GoogleFonts.inter( fontSize: 13, height: 1.45, fontWeight: FontWeight.w400, color: Colors.white, ), ), ), ), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), margin: const EdgeInsets.only(bottom: 6), preferBelow: false, verticalOffset: 10, waitDuration: const Duration(milliseconds: 250), showDuration: const Duration(seconds: 10), decoration: BoxDecoration( color: const Color(0xFF334155), borderRadius: BorderRadius.circular(8), boxShadow: const [ BoxShadow( color: Color(0x40000000), blurRadius: 10, offset: Offset(0, 3), ), ], ), child: textWidget, ), flex: flex, ); } Widget _pendingDataRow(Map item, int sno) { final createdRaw = _pickStr(item, [ 'created_at', 'created_date', 'created_on', ]); final accidentRaw = _pickStr(item, [ 'date_of_incident', 'accident_date', 'accident_datetime', 'date_of_accident', ]); final insured = _pickStr(item, [ 'insured_name', 'customer_name', 'proposer_name', ]); final remarks = _pickStr(item, ['remark']); return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ _cellSno(Text('$sno', style: _dataStyle)), _cellText( createdRaw != '-' ? _formatDate(createdRaw) : '-', flex: 2, maxLines: 2, ), _cell( _claimTypeChip(_pickStr(item, ['claim_type_value'])), flex: 2, ), _cellText(_pickStr(item, ['reg_no']), flex: 2, maxLines: 2), _cellText(insured, flex: 2, maxLines: 2, fontWeight: FontWeight.w600), _cellText( _pickStr(item, ['policy_no', 'policy_number']), flex: 2, maxLines: 2, ), _cellText( _pickStr(item, ['claim_number']), flex: 2, maxLines: 2, ), _cellText( accidentRaw != '-' ? _formatDate(accidentRaw) : '-', flex: 2, maxLines: 2, ), _cellText(_pickStr(item, ['insurer_short_name']), flex: 2, maxLines: 2), _cellText(_pickStr(item, ['broker_name', 'broker']), flex: 2, maxLines: 2), _cellText( _pickStr(item, ['agent_name', 'partner_name', 'partner']), flex: 2, maxLines: 2, ), _cellRemarks(remarks, flex: 2), _cell( _statusChip(_pickStr(item, ['claim_status_value'])), flex: 2, ), _pendingDaysCell(item), _cell(Center(child: _actionIcons(item)), flex: 2), ], ); } // Widget _settledDataRow(Map item, int sno) { // final accidentRaw = _pickStr(item, [ // 'date_of_incident', // 'accident_date', // 'accident_datetime', // 'date_of_accident', // ]); // final insured = _pickStr(item, [ // 'insured_name', // 'customer_name', // 'proposer_name', // ]); // final remarks = _pickStr(item, ['remarks', 'claim_description']); // final settled = _pickStr(item, [ // 'settled_amount', // 'claim_amount', // 'settlement_amount', // ]); // return Row( // crossAxisAlignment: CrossAxisAlignment.center, // children: [ // _cellSno(Text('$sno', style: _dataStyle)), // _cellText(_pickStr(item, ['reg_no']), flex: 2, maxLines: 2), // _cellText(insured, flex: 2, maxLines: 2, fontWeight: FontWeight.w600), // _cellText(_pickStr(item, ['policy_no']), flex: 2, maxLines: 2), // _cellText( // accidentRaw != '-' ? _formatDate(accidentRaw) : '-', // flex: 2, // maxLines: 2, // ), // _cellText(_pickStr(item, ['insurer_short_name']), flex: 2, maxLines: 2), // _cellText(_pickStr(item, ['broker_name', 'broker']), flex: 2, maxLines: 2), // _cellText( // _pickStr(item, ['agent_name', 'partner_name', 'partner']), // flex: 2, // maxLines: 2, // ), // _cellRemarks(remarks, flex: 2), // _cell( // _statusChip(_pickStr(item, ['claim_status_value'])), // flex: 2, // ), // _pendingDaysCell(item), // _cellText(settled == '-' ? '-' : settled, flex: 2, maxLines: 2), // _cell(_actionIcons(item), flex: 1), // ], // ); // } Widget _actionIcons(Map item) { return Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 28, minHeight: 28), tooltip: 'Edit', icon: const Icon(Icons.edit_outlined, color: _teal, size: 18), onPressed: () { showDialog( context: context, builder: (ctx) => AddClaimStepperDialog( managerId: managerId, userId: userId, role: roleId, claimToEdit: item, onSuccess: refresh, ), ); }, ), if (_canShowDeleteAction) const SizedBox(width: 6), // IconButton( // padding: EdgeInsets.zero, // constraints: const BoxConstraints(minWidth: 30, minHeight: 30), // tooltip: 'Upload', // icon: const Icon(Icons.cloud_upload_outlined, color: _teal, size: 18), // onPressed: () {}, // ), // IconButton( // padding: EdgeInsets.zero, // constraints: const BoxConstraints(minWidth: 30, minHeight: 30), // tooltip: 'Download', // icon: const Icon( // Icons.cloud_download_outlined, // color: _actionDownloadBlue, // size: 18, // ), // onPressed: () {}, // ), if (_canShowDeleteAction) IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 28, minHeight: 28), tooltip: 'Delete', icon: const Icon( Icons.delete_outline, color: Color(0xFFC62828), size: 18, ), onPressed: () { final claimId = _pickStr(item, ['id']); _confirmDelete(context, claimId); }, ), ], ); } Widget _claimTypeChip(String text) { final t = text.toLowerCase(); Color bg; Color fg; if (t.contains('theft')) { bg = const Color(0xFFFFEBEE); fg = const Color(0xFFC62828); } else if (t.contains('tp') && !t.contains('own')) { bg = const Color(0xFFE3F2FD); fg = const Color(0xFF1565C0); } else if (t.contains('od') || t.contains('own damage')) { bg = const Color(0xFFF3E5F5); fg = const Color(0xFF6A1B9A); } else { bg = const Color(0xFFF3E5F5); fg = const Color(0xFF6A1B9A); } return Align( alignment: Alignment.centerLeft, child: _pillWrapWords(text, bg, fg), ); } String _statusDisplayLabel(String text) { final t = text.trim(); if (t.isEmpty || t == '-') return t.isEmpty ? '-' : t; final compact = t.replaceAll(RegExp(r'\s+'), '').toUpperCase(); if (compact == 'INPROGRESS') return 'IN PROGRESS'; return t; } Widget _statusChip(String text) { final label = _statusDisplayLabel(text); final t = label.toLowerCase(); Color bg; Color fg; if (t.contains('reject')) { bg = const Color(0xFFFFEBEE); fg = const Color(0xFFC62828); } else if (t.contains('open')) { bg = const Color(0xFFFFF3E0); fg = const Color(0xFFEF6C00); } else if (t.contains('progress')) { bg = const Color(0xFFF3E5F5); fg = const Color(0xFF6A1B9A); } else if (t.contains('settled')) { bg = const Color(0xFFE8F5E9); fg = const Color(0xFF2E7D32); } else { bg = const Color(0xFFF1F5F9); fg = const Color(0xFF475569); } return Align( alignment: Alignment.centerLeft, child: _pill(label, bg, fg), ); } Widget _pill(String text, Color bg, Color fg, {bool showFull = false}) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(20), ), child: Text( text, maxLines: 1, softWrap: false, overflow: TextOverflow.visible, style: GoogleFonts.inter( fontSize: 10.5, fontWeight: FontWeight.w600, color: fg, ), ), ); } /// Pill label that wraps only between words, never inside a word. Widget _pillWrapWords(String text, Color bg, Color fg) { final style = GoogleFonts.inter( fontSize: 10.5, fontWeight: FontWeight.w600, color: fg, ); final words = text.trim().split(RegExp(r'\s+')).where((w) => w.isNotEmpty); if (words.isEmpty) { return _pill('-', bg, fg); } return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(20), ), child: Wrap( spacing: 4, runSpacing: 2, crossAxisAlignment: WrapCrossAlignment.center, children: words .map((w) => Text(w, style: style)) .toList(), ), ); } TextStyle get _dataStyle => GoogleFonts.inter( fontSize: 11.5, fontWeight: FontWeight.w400, color: const Color(0xFF0F172A), ); TextStyle get _dataBold => GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w700, color: const Color(0xFF0F172A), ); Widget _buildMobileList() { if (filteredData.isEmpty) { return Center( child: Text( 'No claims match your filters.', style: GoogleFonts.inter(color: _muted), ), ); } final rows = _paginatedSorted; final startSerial = (currentPage - 1) * itemsPerPage; return ListView.separated( itemCount: rows.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (context, index) { final item = rows[index]; final sno = startSerial + index + 1; return _mobileCard(item, sno); }, ); } Widget _mobileCard(Map item, int sno) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all(color: _border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('#$sno', style: _dataStyle.copyWith(color: _muted)), const Spacer(), _statusChip(_pickStr(item, ['claim_status_value'])), ], ), const SizedBox(height: 8), Text( _pickStr(item, ['reg_no']), style: _dataStyle.copyWith( fontWeight: FontWeight.w700, fontSize: 14, ), ), const SizedBox(height: 4), Text( _pickStr(item, [ 'insured_name', 'customer_name', 'proposer_name', ]), style: _dataStyle.copyWith(fontWeight: FontWeight.w500), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: [ _claimTypeChip(_pickStr(item, ['claim_type_value'])), ], ), const Divider(height: 20), _mobilePair( 'Policy', _pickStr(item, ['policy_no', 'policy_number']), ), _mobilePair( 'Claim No', _pickStr(item, ['claim_number']), ), _mobilePair('Insurer', _pickStr(item, ['insurer_short_name'])), _mobilePair('Pending days', _pendingDaysLabel(item)), // if (!_pendingTab) ...[ // _mobilePair( // 'Settled (₹)', // _pickStr(item, [ // 'settled_amount', // 'claim_amount', // 'settlement_amount', // ]), // ), // ], const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: _actionIcons(item), ), ], ), ); } Widget _mobilePair(String k, String v) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 88, child: Text( k, style: GoogleFonts.inter( fontSize: 11, color: _muted, fontWeight: FontWeight.w500, ), ), ), Expanded( child: Text(v, style: _dataStyle), ), ], ), ); } }