import 'package:flutter/material.dart'; import '../models/client.dart'; import '../theme/app_theme.dart'; import '../utils/snackbar_utils.dart'; import 'survey_screen.dart' show ConsentBadge, ProviderPickerSheet; class ClientsScreen extends StatefulWidget { final List clients; final void Function(String clientId, String notes) onUpdateClientNotes; final void Function( String clientId, double parcelVolume, int activeContracts, String provider, String notes, ) onCompleteClientProfile; final void Function(int) onNavigate; final bool backendReady; final bool isActive; const ClientsScreen({ super.key, required this.clients, required this.onUpdateClientNotes, required this.onCompleteClientProfile, required this.onNavigate, required this.backendReady, this.isActive = false, }); @override State createState() => _ClientsScreenState(); } class _ClientsScreenState extends State { final _searchController = TextEditingController(); final ScrollController _scrollController = ScrollController(); String _search = ''; String? _selectedCity; BusinessType? _selectedType; @override void didUpdateWidget(covariant ClientsScreen oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isActive && !oldWidget.isActive) { if (_scrollController.hasClients) { _scrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); } } } @override void dispose() { _searchController.dispose(); _scrollController.dispose(); super.dispose(); } /// Unique city strings from the client list for dynamic filter chips. List get _uniqueCities { final seen = {}; return widget.clients .map((c) => c.city) .where((c) => c.isNotEmpty && seen.add(c)) .toList(); } List get _filtered { return widget.clients.where((c) { final q = _search.toLowerCase(); final matchSearch = _search.isEmpty || c.name.toLowerCase().contains(q) || c.city.toLowerCase().contains(q) || c.provider.toLowerCase().contains(q) || c.neighbourhood.toLowerCase().contains(q); final matchCity = _selectedCity == null || c.city == _selectedCity; final matchType = _selectedType == null || c.businessType == _selectedType; return matchSearch && matchCity && matchType; }).toList(); } void _openClient(Client client) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => _ClientDetailSheet( client: client, onSaveNotes: (notes) => widget.onUpdateClientNotes(client.id, notes), onCompleteProfile: (vol, contracts, provider, notes) { widget.onCompleteClientProfile( client.id, vol, contracts, provider, notes); }, ), ); } @override Widget build(BuildContext context) { final filtered = _filtered; final cities = _uniqueCities; return SingleChildScrollView( controller: _scrollController, padding: const EdgeInsets.fromLTRB(16, 20, 16, 100), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ WideTopBanner( backendReady: widget.backendReady, subtitle: 'CLIENT DATABASE', ), const SizedBox(height: 16), Text('Search, audit, and coordinate logistics survey reports.', style: TextStyle(fontSize: 13, color: AppColors.textSecondary)), const SizedBox(height: 16), // Search TextField( controller: _searchController, onChanged: (v) => setState(() => _search = v), decoration: InputDecoration( hintText: 'Search company, city, area or provider…', hintStyle: TextStyle(color: AppColors.muted, fontSize: 13), prefixIcon: Icon(Icons.search, color: AppColors.muted, size: 20), ), ), const SizedBox(height: 14), // City filter — built dynamically from actual client data if (cities.isNotEmpty) ...[ Text('FILTER BY CITY', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 1.2, color: AppColors.textSecondary)), const SizedBox(height: 6), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _FilterChip( label: 'All', selected: _selectedCity == null, activeColor: AppColors.primary, onTap: () => setState(() => _selectedCity = null), ), ...cities.map((city) => _FilterChip( label: city, selected: _selectedCity == city, activeColor: AppColors.primary, onTap: () => setState(() => _selectedCity = city), )), ], ), ), const SizedBox(height: 12), ], // Business type filter Text('FILTER BY CLASSIFICATION', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 1.2, color: AppColors.textSecondary)), const SizedBox(height: 6), SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _FilterChip( label: 'All', selected: _selectedType == null, activeColor: AppColors.green, onTap: () => setState(() => _selectedType = null), ), ...BusinessType.values.map( (t) => _FilterChip( label: t.label, selected: _selectedType == t, activeColor: AppColors.green, onTap: () => setState(() => _selectedType = t), ), ), ], ), ), const SizedBox(height: 16), // Results Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('RESULTS (${filtered.length})', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), Text('CURRENT STACK', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), ], ), const SizedBox(height: 10), if (filtered.isEmpty) _EmptyState(onNavigate: widget.onNavigate) else Column( children: filtered .map((c) => _ClientCard( client: c, onTap: () => _openClient(c))) .toList(), ), ], ), ); } } // ── Filter chip ─────────────────────────────────────────────────────────────── class _FilterChip extends StatelessWidget { final String label; final bool selected; final Color activeColor; final VoidCallback onTap; const _FilterChip({ required this.label, required this.selected, required this.activeColor, required this.onTap, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(right: 6), child: GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 7), decoration: BoxDecoration( color: selected ? activeColor : const Color(0xFFEFF4FF), borderRadius: BorderRadius.circular(20), ), child: Text(label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: selected ? Colors.white : AppColors.textSecondary)), ), ), ); } } // ── Client card ─────────────────────────────────────────────────────────────── class _ClientCard extends StatelessWidget { final Client client; final VoidCallback onTap; const _ClientCard({required this.client, required this.onTap}); @override Widget build(BuildContext context) { Color typeBg; Color typeFg; switch (client.businessType) { case BusinessType.ecommerce: typeBg = const Color(0xFFFFDAD7); typeFg = const Color(0xFF930013); break; case BusinessType.manufacturing: typeBg = const Color(0xFFD8F9EE); typeFg = const Color(0xFF00504A); break; case BusinessType.wholesale: typeBg = const Color(0xFFD8E3FB); typeFg = const Color(0xFF3C475A); break; case BusinessType.retail: typeBg = const Color(0xFFEFF4FF); typeFg = const Color(0xFF545F73); break; } final isBasic = client.dataConsent == DataConsent.basicOnly; return Padding( padding: const EdgeInsets.only(bottom: 10), child: GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isBasic ? const Color(0xFFFFB800).withValues(alpha: 0.35) : AppColors.border.withValues(alpha: 0.25), ), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name + type badge + consent badge Row(children: [ Flexible( child: Text(client.name, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.darkBlue), maxLines: 1, overflow: TextOverflow.ellipsis), ), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( color: typeBg, borderRadius: BorderRadius.circular(4)), child: Text(client.businessType.label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: typeFg)), ), if (isBasic) ...[ const SizedBox(width: 5), const ConsentBadge(small: true), ], ]), const SizedBox(height: 6), // City + neighbourhood + route/provider row Wrap(spacing: 10, runSpacing: 4, children: [ if (client.city.isNotEmpty) _CardMeta( icon: Icons.location_on, color: AppColors.primary, text: client.neighbourhood.isNotEmpty ? '${client.city} · ${client.neighbourhood}' : client.city), if (client.frequency.isNotEmpty) _CardMeta( icon: Icons.repeat, color: AppColors.textSecondary, text: client.frequency), if (!isBasic) _CardMeta( icon: Icons.local_shipping, color: AppColors.green, text: client.provider), if (isBasic) _CardMeta( icon: Icons.lock_outline, color: const Color(0xFFE87C00), text: 'Not disclosed'), ]), ], ), ), const SizedBox(width: 10), if (isBasic) Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 8), decoration: BoxDecoration( color: const Color(0xFFFFF8ED), borderRadius: BorderRadius.circular(10), border: Border.all( color: const Color(0xFFFFB800).withValues(alpha: 0.4)), ), child: const Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_circle_outline, size: 16, color: Color(0xFFE87C00)), SizedBox(height: 2), Text('Complete', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: Color(0xFFE87C00))), ], ), ) else Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ RichText( text: TextSpan(children: [ TextSpan( text: '${client.parcelVolume.round()}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.darkBlue, fontFamily: 'monospace')), TextSpan( text: ' /wk', style: TextStyle( fontSize: 10, color: AppColors.textSecondary)), ]), ), Text('${client.activeContracts} contracts', style: TextStyle( fontSize: 10, color: AppColors.textSecondary)), ], ), ], ), ), ), ); } } class _CardMeta extends StatelessWidget { final IconData icon; final Color color; final String text; const _CardMeta( {required this.icon, required this.color, required this.text}); @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 12, color: color), const SizedBox(width: 2), Text(text, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: color), maxLines: 1, overflow: TextOverflow.ellipsis), ], ); } } // ── Empty state ─────────────────────────────────────────────────────────────── class _EmptyState extends StatelessWidget { final void Function(int) onNavigate; const _EmptyState({required this.onNavigate}); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(40), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border.withValues(alpha: 0.15)), ), child: Column(children: [ Container( width: 48, height: 48, decoration: const BoxDecoration( color: Color(0xFFEFF4FF), shape: BoxShape.circle), child: Icon(Icons.filter_list, size: 24, color: AppColors.primary), ), const SizedBox(height: 12), const Text('No matching clients found', style: TextStyle( fontWeight: FontWeight.w700, color: AppColors.darkBlue)), const SizedBox(height: 4), Text('Try adjusting search or filters.', textAlign: TextAlign.center, style: TextStyle( fontSize: 12, color: AppColors.textSecondary)), const SizedBox(height: 14), ElevatedButton( onPressed: () => onNavigate(2), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), child: const Text('Add New Client', style: TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700)), ), ]), ); } } // ── Detail bottom sheet ─────────────────────────────────────────────────────── class _ClientDetailSheet extends StatefulWidget { final Client client; final void Function(String notes) onSaveNotes; final void Function(double vol, int contracts, String provider, String notes) onCompleteProfile; const _ClientDetailSheet({ required this.client, required this.onSaveNotes, required this.onCompleteProfile, }); @override State<_ClientDetailSheet> createState() => _ClientDetailSheetState(); } class _ClientDetailSheetState extends State<_ClientDetailSheet> { late final TextEditingController _notesCtrl; // Complete-profile form (basicOnly clients) bool _showCompleteForm = false; double _upgradeVolume = 450; final _upgradeContractsCtrl = TextEditingController(text: '12'); String _upgradeProvider = 'DTDC Express'; final _upgradeCustomProviderCtrl = TextEditingController(); final _upgradeNotesCtrl = TextEditingController(); @override void initState() { super.initState(); _notesCtrl = TextEditingController(text: widget.client.notes); } @override void dispose() { _notesCtrl.dispose(); _upgradeContractsCtrl.dispose(); _upgradeCustomProviderCtrl.dispose(); _upgradeNotesCtrl.dispose(); super.dispose(); } Color get _typeColor { switch (widget.client.businessType) { case BusinessType.ecommerce: return const Color(0xFF930013); case BusinessType.manufacturing: return const Color(0xFF00504A); case BusinessType.wholesale: return const Color(0xFF3C475A); case BusinessType.retail: return const Color(0xFF545F73); } } Color get _typeBg { switch (widget.client.businessType) { case BusinessType.ecommerce: return const Color(0xFFFFDAD7); case BusinessType.manufacturing: return const Color(0xFFD8F9EE); case BusinessType.wholesale: return const Color(0xFFD8E3FB); case BusinessType.retail: return const Color(0xFFEFF4FF); } } @override Widget build(BuildContext context) { final client = widget.client; final isBasic = client.dataConsent == DataConsent.basicOnly; return DraggableScrollableSheet( initialChildSize: 0.88, minChildSize: 0.5, maxChildSize: 0.97, builder: (_, scrollCtrl) => Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( children: [ const SizedBox(height: 10), Container( width: 40, height: 4, decoration: BoxDecoration( color: AppColors.muted.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 12), // Header Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row(children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3), decoration: BoxDecoration( color: _typeBg, borderRadius: BorderRadius.circular(4)), child: Text( client.businessType.label.toUpperCase(), style: TextStyle( fontSize: 9, fontWeight: FontWeight.w800, letterSpacing: 1, color: _typeColor), ), ), if (isBasic) ...[ const SizedBox(width: 6), const ConsentBadge(), ], ]), const SizedBox(height: 6), Text(client.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), ], ), ), GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: 32, height: 32, decoration: const BoxDecoration( color: Color(0xFFF8F9FF), shape: BoxShape.circle), child: Icon(Icons.close, size: 16, color: AppColors.textSecondary), ), ), ]), ), Divider( height: 20, color: AppColors.border.withValues(alpha: 0.15)), Expanded( child: ListView( controller: scrollCtrl, padding: const EdgeInsets.symmetric(horizontal: 20), children: [ // Partial info banner if (isBasic) _buildBasicOnlyBanner(), // ── Contact & Identity ─────────────────────────────── _sectionLabel('CONTACT & IDENTITY'), const SizedBox(height: 8), Row(children: [ Expanded( child: _InfoTile( icon: Icons.location_city_outlined, label: 'CITY', value: client.city.isNotEmpty ? client.city : '—')), const SizedBox(width: 10), Expanded( child: _InfoTile( icon: Icons.map_outlined, label: 'NEIGHBOURHOOD', value: client.neighbourhood.isNotEmpty ? client.neighbourhood : '—')), ]), const SizedBox(height: 10), Row(children: [ Expanded( child: _InfoTile( icon: Icons.phone_outlined, label: 'PHONE', value: client.phone.isNotEmpty ? client.phone : '—')), const SizedBox(width: 10), Expanded( child: _InfoTile( icon: Icons.repeat, label: 'FREQUENCY', value: client.frequency.isNotEmpty ? client.frequency : '—')), ]), if (client.logisticsSegment.isNotEmpty) ...[ const SizedBox(height: 10), Row( children: [ Expanded( child: _InfoTile( icon: client.logisticsSegment == 'First Mile' ? Icons.warehouse_outlined : client.logisticsSegment == 'Middle Mile' ? Icons.local_shipping_outlined : client.logisticsSegment == 'Last Mile' ? Icons.markunread_mailbox_outlined : Icons.category_outlined, label: 'LOGISTICS SEGMENT', value: client.logisticsSegment, ), ), ], ), ], // ── Transit route ──────────────────────────────────── if (client.transitFrom.isNotEmpty || client.transitTo.isNotEmpty) ...[ const SizedBox(height: 10), Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFF8F9FF), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.route, size: 12, color: AppColors.primary), const SizedBox(width: 4), Text('USUAL TRANSIT ROUTE', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), ]), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: _RouteStop( icon: Icons.flight_takeoff, label: 'FROM', value: client.transitFrom.isNotEmpty ? client.transitFrom : '—'), ), Container( width: 24, height: 1, margin: const EdgeInsets.only(top: 14, left: 8, right: 8), color: AppColors.border.withValues(alpha: 0.3), ), Expanded( child: _RouteStop( icon: Icons.flight_land, label: 'TO', value: client.transitTo.isNotEmpty ? client.transitTo : '—'), ), ], ), ], ), ), ], // ── Logistics details ──────────────────────────────── const SizedBox(height: 14), _sectionLabel('LOGISTICS DETAILS'), const SizedBox(height: 8), Row(children: [ Expanded( child: isBasic ? _LockedTile(label: 'WEEKLY VOLUME') : _InfoTile( icon: Icons.inventory_2_outlined, label: 'WEEKLY VOLUME', value: '${client.parcelVolume.round()} /wk')), const SizedBox(width: 10), Expanded( child: isBasic ? _LockedTile(label: 'CONTRACTS') : _InfoTile( icon: Icons.description_outlined, label: 'CONTRACTS', value: '${client.activeContracts} active')), ]), const SizedBox(height: 10), // Provider tile if (isBasic) Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFFFF8ED), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFFFB800) .withValues(alpha: 0.3)), ), child: Row(children: [ const Icon(Icons.lock_outline, size: 16, color: Color(0xFFE87C00)), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('LOGISTICS PROVIDER', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), const SizedBox(height: 4), const Text('Not disclosed by client', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFFE87C00))), ], ), ]), ) else Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFEFF4FF), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('LOGISTICS PARTNER PROVIDER', style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), const SizedBox(height: 4), Text(client.provider, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), ], ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFFFE4E6), borderRadius: BorderRadius.circular(20), ), child: Text(client.efficiency, style: const TextStyle( fontSize: 9, fontWeight: FontWeight.w800, color: AppColors.primary)), ), ], ), ), const SizedBox(height: 10), // Status matrix Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: const Color(0xFFF8F9FF), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.auto_awesome, size: 12, color: AppColors.primary), const SizedBox(width: 4), Text('STATUS MATRIX', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), ]), const SizedBox(height: 8), _StatusRow( label: 'Survey status', value: 'SUBMITTED', valueColor: AppColors.green), _StatusRow( label: 'Last checked', value: client.lastUpdated), ], ), ), // Complete profile section (basicOnly) if (isBasic) ...[ const SizedBox(height: 14), _buildCompleteProfileSection(context), ], // Notes (full clients only) if (!isBasic) ...[ const SizedBox(height: 14), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('CRM REMARKS', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), Text('Auto-saved', style: TextStyle( fontSize: 10, color: AppColors.textSecondary)), ], ), const SizedBox(height: 6), TextField( controller: _notesCtrl, maxLines: 4, decoration: const InputDecoration( hintText: 'e.g. Needs immediate review or transit optimisation…', ), ), ], const SizedBox(height: 80), ], ), ), // Bottom action bar Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 28), decoration: BoxDecoration( color: const Color(0xFFF8F9FF), border: Border( top: BorderSide( color: AppColors.border.withValues(alpha: 0.15))), ), child: Row(children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( side: BorderSide(color: AppColors.border), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), minimumSize: const Size.fromHeight(44), ), child: Text('Close', style: TextStyle( color: AppColors.textSecondary, fontWeight: FontWeight.w700, fontSize: 12)), ), ), if (!isBasic) ...[ const SizedBox(width: 10), Expanded( child: ElevatedButton.icon( onPressed: () { widget.onSaveNotes(_notesCtrl.text); Navigator.pop(context); if (mounted) { SnackbarUtils.showSuccess(context, 'Remarks saved!'); } }, icon: const Icon(Icons.save, size: 14, color: Colors.white), label: const Text('Save Notes', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12)), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), minimumSize: const Size.fromHeight(44), elevation: 1, ), ), ), ], ]), ), ], ), ), ); } Widget _buildBasicOnlyBanner() { return Container( margin: const EdgeInsets.only(bottom: 14), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( color: const Color(0xFFFFF8ED), borderRadius: BorderRadius.circular(12), border: Border.all( color: const Color(0xFFFFB800).withValues(alpha: 0.5)), ), child: Row(children: [ const Icon(Icons.lock_outline, size: 16, color: Color(0xFFE87C00)), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Partial Information', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: Color(0xFFE87C00))), Text( 'Commercial logistics details not yet shared. Use "Complete Profile" below.', style: TextStyle( fontSize: 11, color: AppColors.textSecondary), ), ], ), ), ]), ); } Future _showUpgradeProviderSelectorSheet() async { final picked = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => ProviderPickerSheet( initialProvider: _upgradeProvider, ), ); if (picked != null) { setState(() { _upgradeProvider = picked; }); } } Widget _buildCompleteProfileSection(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () => setState(() => _showCompleteForm = !_showCompleteForm), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF0B1C30), Color(0xFF1E3A5F)]), borderRadius: BorderRadius.circular(14), ), child: Row(children: [ Container( width: 38, height: 38, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10)), child: Icon( _showCompleteForm ? Icons.keyboard_arrow_up : Icons.add_circle_outline, size: 20, color: Colors.white, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Complete Profile', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: Colors.white)), Text( _showCompleteForm ? 'Tap to collapse' : 'Collect volume, provider & notes', style: TextStyle( fontSize: 11, color: Colors.white.withValues(alpha: 0.65)), ), ], ), ), Icon( _showCompleteForm ? Icons.expand_less : Icons.expand_more, color: Colors.white.withValues(alpha: 0.7)), ]), ), ), if (_showCompleteForm) Container( margin: const EdgeInsets.only(top: 2), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFFF8F9FF), borderRadius: const BorderRadius.vertical( bottom: Radius.circular(14)), border: Border.all( color: AppColors.border.withValues(alpha: 0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Volume Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('Weekly Parcel Volume', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), Text('${_upgradeVolume.round()} /wk', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, fontFamily: 'monospace', color: AppColors.primary)), ], ), const SizedBox(height: 6), SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: AppColors.primary, inactiveTrackColor: const Color(0xFFE8EEFF), thumbColor: AppColors.primary, overlayColor: AppColors.primary.withValues(alpha: 0.1), thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 9), ), child: Slider( value: _upgradeVolume, min: 50, max: 1200, divisions: 115, onChanged: (v) => setState(() => _upgradeVolume = v), ), ), const SizedBox(height: 12), // Contracts const Text('Active Service Contracts', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), const SizedBox(height: 6), TextField( controller: _upgradeContractsCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration( prefixIcon: Icon(Icons.description_outlined, size: 16), ), ), const SizedBox(height: 12), // Provider const Text('Primary Service Provider', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), const SizedBox(height: 6), InkWell( onTap: _showUpgradeProviderSelectorSheet, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border.withValues(alpha: 0.5)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( _upgradeProvider, style: const TextStyle( fontSize: 13, color: AppColors.darkBlue, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ), Icon(Icons.arrow_drop_down, color: AppColors.textSecondary), ], ), ), ), if (_upgradeProvider == 'Others') ...[ const SizedBox(height: 12), const Text('Specify Other Provider', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), const SizedBox(height: 6), TextField( controller: _upgradeCustomProviderCtrl, textCapitalization: TextCapitalization.words, decoration: const InputDecoration( hintText: 'e.g. My Local Logistics Operator', prefixIcon: Icon(Icons.edit_note, size: 18), ), ), ], const SizedBox(height: 12), // Notes const Text('Survey Remarks', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), const SizedBox(height: 6), TextField( controller: _upgradeNotesCtrl, maxLines: 3, decoration: const InputDecoration( hintText: 'Additional observations…', ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 44, child: ElevatedButton.icon( onPressed: () { String providerName = _upgradeProvider == 'Others' ? _upgradeCustomProviderCtrl.text.trim() : _upgradeProvider; if (providerName.isEmpty) providerName = 'Others'; widget.onCompleteProfile( _upgradeVolume, int.tryParse( _upgradeContractsCtrl.text) ?? 12, providerName, _upgradeNotesCtrl.text.trim(), ); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text( 'Profile upgraded to full details!'), backgroundColor: AppColors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), ); }, icon: const Icon(Icons.lock_open, size: 14, color: Colors.white), label: const Text('Save Full Profile', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w700)), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0B1C30), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), elevation: 1, ), ), ), ], ), ), ], ); } Widget _sectionLabel(String text) => Text(text, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: 1.2, color: AppColors.textSecondary)); } // ── Info tiles ──────────────────────────────────────────────────────────────── class _InfoTile extends StatelessWidget { final IconData icon; final String label; final String value; const _InfoTile( {required this.icon, required this.label, required this.value}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFEFF4FF), borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(icon, size: 10, color: AppColors.textSecondary), const SizedBox(width: 4), Text(label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), ]), const SizedBox(height: 4), Text(value, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.darkBlue), maxLines: 1, overflow: TextOverflow.ellipsis), ], ), ); } } class _LockedTile extends StatelessWidget { final String label; const _LockedTile({required this.label}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF8ED), borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFFFB800).withValues(alpha: 0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), const SizedBox(height: 4), Row(children: [ const Icon(Icons.lock_outline, size: 12, color: Color(0xFFE87C00)), const SizedBox(width: 4), const Text('Hidden', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: Color(0xFFE87C00))), ]), ], ), ); } } class _RouteStop extends StatelessWidget { final IconData icon; final String label; final String value; const _RouteStop( {required this.icon, required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(icon, size: 10, color: AppColors.primary), const SizedBox(width: 3), Text(label, style: TextStyle( fontSize: 9, fontWeight: FontWeight.w700, color: AppColors.textSecondary)), ]), const SizedBox(height: 2), Text(value, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.darkBlue)), ], ); } } class _StatusRow extends StatelessWidget { final String label; final String value; final Color? valueColor; const _StatusRow( {required this.label, required this.value, this.valueColor}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle( fontSize: 11, color: AppColors.textSecondary)), Text(value, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: valueColor ?? AppColors.darkBlue)), ], ), ); } }