import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:geolocator/geolocator.dart'; import 'package:go_router/go_router.dart'; import 'package:niogu_app/core/components/top_back_bar_app.dart'; import 'package:niogu_app/core/constants/app_color.dart'; import 'package:niogu_app/core/constants/app_font_size.dart'; import 'package:niogu_app/core/constants/app_temp.dart'; import 'package:niogu_app/core/router/app_route.dart'; import 'package:niogu_app/core/enums/customer_source.dart'; import 'package:niogu_app/core/utils/log_message.dart'; import 'package:niogu_app/core/widgets/custom_snackbar.dart'; import 'package:niogu_app/core/widgets/custom_text_form_field.dart'; import 'package:niogu_app/features/customer/domain/entities/customer.dart'; import 'package:niogu_app/features/customer/presentation/providers/customer_provider.dart'; import 'package:niogu_app/features/customer/presentation/widgets/customer_activity.dart'; import 'package:niogu_app/features/customer/presentation/widgets/edit_customer_shimmer.dart'; import 'package:sizer/sizer.dart'; class EditCustomerScreen extends ConsumerStatefulWidget { final String customerId; const EditCustomerScreen({super.key, required this.customerId}); @override ConsumerState createState() => _EditCustomerScreenState(); } class _EditCustomerScreenState extends ConsumerState { final GlobalKey _formKey = GlobalKey(); final TextEditingController _nameController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _phoneController = TextEditingController(); bool _isOnlineCustomer = false; bool _isLoading = true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 800), () { _fetchCustomer(); }); }); } @override void dispose() { // TODO: implement dispose _nameController.dispose(); _emailController.dispose(); _phoneController.dispose(); super.dispose(); } Future _fetchCustomer() async { try { final customerRepository = ref.read(customerRepositoryProvider); final customer = await customerRepository.fetchCustomerById( widget.customerId, ); if (!mounted) return; _nameController.text = customer.name; _emailController.text = customer.email; _phoneController.text = customer.phoneNumber; _isOnlineCustomer = customer.customerSource == CustomerSource.online; setState(() { _isLoading = false; }); } catch (e, st) { if (!mounted) return; setState(() { _isLoading = false; }); LogMessage.log.e(e.toString(), error: e, stackTrace: st); CustomSnackbar.showError(context, "Ups, terjadi kesalahan"); context.pop(); } } String calculateDistance( double startLat, double startLng, double endLat, double endLng, ) { double distanceInMeters = Geolocator.distanceBetween( startLat, startLng, endLat, endLng, ); if (distanceInMeters >= 1000) { return "${(distanceInMeters / 1000).toStringAsFixed(2)} km"; } else { return "${distanceInMeters.toStringAsFixed(0)} m"; } } Future _updateCustomer() async { if (!_formKey.currentState!.validate()) return; try { await ref .read(customerControllerProvider.notifier) .saveCustomer( UpsertCustomer( id: widget.customerId, name: _nameController.text.trim(), email: _emailController.text.trim(), phoneNumber: _phoneController.text.trim(), ), ); if (!mounted) return; CustomSnackbar.showSuccess(context, "Pelanggan berhasil diubah"); context.pop(); } catch (e, st) { LogMessage.log.e(e.toString(), error: e, stackTrace: st); CustomSnackbar.showError(context, "Ups, terjadi kesalahan"); context.pop(); } } Future _deleteCustomer() async { try { ref .read(customerControllerProvider.notifier) .deleteCustomer(widget.customerId); if (!mounted) return; CustomSnackbar.showSuccess(context, "Pelanggan berhasil dihapus"); Navigator.pop(context); context.pop(); } catch (e, st) { LogMessage.log.e(e.toString(), error: e, stackTrace: st); CustomSnackbar.showError(context, "Ups, terjadi kesalahan"); Navigator.pop(context); context.pop(); } } void _showDeleteConfirmation(BuildContext context) { final bool isTablet = 100.w >= 600; final customerControllerState = ref.watch(customerControllerProvider); showDialog( context: context, builder: (BuildContext context) { return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4.5.w), ), elevation: 0, backgroundColor: Colors.transparent, child: Container( padding: EdgeInsets.all(6.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4.5.w), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: EdgeInsets.all(4.w), decoration: BoxDecoration( color: Colors.red[50], shape: BoxShape.circle, ), child: Icon( Icons.warning_amber_rounded, color: Colors.red, size: 10.w, ), ), SizedBox(height: 2.h), Text( "Hapus Pelanggan?", style: TextStyle( fontSize: AppFontSize.medium.sp, fontWeight: FontWeight.bold, color: Colors.black87, ), ), SizedBox(height: 1.h), Text( "Tindakan ini tidak dapat dibatalkan. Semua data profil pelanggan akan dihapus dari sistem.", textAlign: TextAlign.center, style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25).sp : (AppFontSize.small - 1.25).sp, color: Colors.grey[600], height: 1.5, ), ), SizedBox(height: 3.h), Row( children: [ Expanded( child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: Colors.grey.shade300, padding: EdgeInsets.symmetric(vertical: 1.5.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.5.w), ), elevation: 0, ), child: Text( "Batal", style: TextStyle( color: Colors.grey[800], fontWeight: FontWeight.bold, fontSize: AppFontSize.medium.sp, ), ), ), ), SizedBox(width: 3.w), Expanded( child: ElevatedButton( onPressed: customerControllerState.isLoading ? null : _deleteCustomer, style: ElevatedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 1.5.h), backgroundColor: Colors.red, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.5.w), ), disabledBackgroundColor: Colors.grey.shade300, ), child: Text( "Ya, Hapus", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: AppFontSize.medium.sp, ), ), ), ), ], ), ], ), ), ); }, ); } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final bool isTablet = 100.w >= 600; final customerAddressesState = ref.watch( customerAddressesProvider(widget.customerId), ); final customerActivitiesState = ref.watch( customerActivitiesProvider(widget.customerId), ); final customerControllerState = ref.watch(customerControllerProvider); return SafeArea( top: false, bottom: true, right: false, left: false, child: Scaffold( backgroundColor: Colors.white, appBar: TopBackBarApp( title: "Detail Pelanggan", onTap: () => context.pop(), ), body: _isLoading ? const EditCustomerShimmer() : SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Form( key: _formKey, child: Column( children: [ Container( width: double.infinity, color: Colors.white, padding: EdgeInsets.symmetric(vertical: 3.h), child: Column( children: [ Stack( alignment: Alignment.bottomRight, children: [ CircleAvatar( radius: 12.w, backgroundColor: Colors.grey[200], child: Icon( Icons.person, size: 15.w, color: Colors.grey[400], ), ), Container( padding: EdgeInsets.all(1.5.w), decoration: BoxDecoration( color: _isOnlineCustomer ? Colors.blue : Colors.orange, shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2, ), ), child: Icon( _isOnlineCustomer ? Icons.language : Icons.storefront, size: 5.w, color: Colors.white, ), ), ], ), SizedBox(height: 1.5.h), Container( padding: EdgeInsets.symmetric( horizontal: 3.w, vertical: 0.5.h, ), decoration: BoxDecoration( color: _isOnlineCustomer ? Colors.blue[50] : Colors.orange[50], borderRadius: BorderRadius.circular(4.5.w), ), child: Text( _isOnlineCustomer ? "Pelanggan Toko Online" : "Pelanggan Offline", style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25).sp : (AppFontSize.small - 1.25).sp, fontWeight: FontWeight.bold, color: _isOnlineCustomer ? Colors.blue[700] : Colors.orange[700], ), ), ), ], ), ), Container( color: Colors.white, padding: EdgeInsets.fromLTRB(5.w, 0, 5.w, 3.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTextFormField( label: "Nama Pelanggan", controller: _nameController, hint: "Masukkan nama", validator: (value) { if (value == null || value.isEmpty) { return "Nama pelanggan belum diisi"; } return null; }, prefixIcon: Icons.person_outline, readOnly: _isOnlineCustomer, ), SizedBox(height: 2.h), CustomTextFormField( label: "Email", controller: _emailController, hint: "Masukkan email (opsional)", keyboardType: TextInputType.emailAddress, prefixIcon: Icons.email_outlined, readOnly: _isOnlineCustomer, ), SizedBox(height: 2.h), CustomTextFormField( label: "No. Handphone / WA", controller: _phoneController, hint: "Masukkan no handphone / wa (opsional)", keyboardType: TextInputType.phone, prefixIcon: Icons.phone_android_outlined, readOnly: _isOnlineCustomer, ), ], ), ), _buildSectionHeader("Alamat Pelanggan"), customerAddressesState.when( data: (addresses) { final int displayAddressCount = addresses.length > 3 ? 3 : addresses.length; final bool hasMore = addresses.length > 3; return addresses.isEmpty ? _buildEmptyOrErrorAddress( Icons.location_off_outlined, "Tidak ada alamat yang tersedia", ) : SizedBox( height: 22.5.h, child: ListView.builder( padding: EdgeInsets.only( left: 5.w, right: 2.w, ), scrollDirection: Axis.horizontal, itemCount: displayAddressCount + (hasMore ? 1 : 0), itemBuilder: (context, index) { if (index < displayAddressCount) { final address = addresses[index]; final String distance = calculateDistance( AppTemp.OUTLET_LATITUDE, AppTemp.OUTLET_LONGITUDE, address.coordinate.latitude, address.coordinate.longitude, ); return Container( width: 75.w, margin: EdgeInsets.only( right: 3.w, ), padding: EdgeInsets.all(3.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular( 2.5.w, ), border: Border.all( color: Colors.grey[200]!, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: EdgeInsets.all( 2.w, ), decoration: BoxDecoration( color: AppColor .primaryColor .withOpacity( 0.1, ), shape: BoxShape .circle, ), child: Icon( Icons.location_on, color: AppColor .primaryColor, size: 5.w, ), ), SizedBox(width: 3.w), Text( address.label, style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25) .sp : (AppFontSize.small - 1.25) .sp, color: Colors.grey[700], ), ), ], ), SizedBox(height: 1.5.h), Text( address.fullAddress, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25) .sp : (AppFontSize.small - 1.25) .sp, color: Colors.grey[700], height: 1.4, ), ), Divider( color: Colors.grey[100], ), Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Row( children: [ Icon( Icons .straighten_rounded, size: 5.w, color: Colors.grey, ), SizedBox(width: 3.w), Text( "$distance", style: TextStyle( fontSize: isTablet ? (AppFontSize.medium - 1.25) .sp : (AppFontSize.small - 1.25) .sp, color: Colors.black, fontWeight: FontWeight .bold, ), ), ], ), TextButton.icon( onPressed: () { context.pushNamed( AppRoute .mapCustomerAddressScreen, extra: address, ); }, icon: Icon( Icons.map_outlined, color: AppColor .primaryColor, size: 5.w, ), label: Text( "Lihat di Peta", style: TextStyle( color: AppColor .primaryColor, fontSize: AppFontSize .small .sp, fontWeight: FontWeight.bold, ), ), ), ], ), ], ), ); } else { return GestureDetector( onTap: () async { FocusScope.of( context, ).unfocus(); await Future.delayed( const Duration( milliseconds: 400, ), ); context.pushNamed( AppRoute .customerAddressesScreen, extra: addresses, ); }, child: Container( width: 35.w, margin: EdgeInsets.only( right: 5.w, ), decoration: BoxDecoration( color: AppColor.primaryColor .withOpacity(0.05), borderRadius: BorderRadius.circular( 2.5.w, ), border: Border.all( color: AppColor.primaryColor .withOpacity(0.2), ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons .arrow_circle_right_outlined, color: AppColor.primaryColor, size: 8.w, ), SizedBox(height: 0.5.h), Text( "${addresses.length - 3} Alamat\nLainnya", textAlign: TextAlign.center, style: TextStyle( color: AppColor .primaryColor, fontWeight: FontWeight.bold, fontSize: isTablet ? (AppFontSize.medium - 1.25) .sp : (AppFontSize.small - 1.25) .sp, ), ), ], ), ), ); } }, ), ); }, error: (error, stackTrace) => _buildEmptyOrErrorAddress( Icons.error_rounded, "Ups, terjadi kesalahan", ), loading: () => const SizedBox(), ), _buildSectionHeader("Aktivitas Pelanggan"), customerActivitiesState.when( data: (activities) { final int totalActivity = activities.length; final int displayActivityCount = totalActivity > 3 ? 3 : totalActivity; return activities.isEmpty ? _buildEmptyOrErrorActivities( Icons.history_rounded, "Belum ada riwayat aktivitas pembelian", ) : Column( children: [ ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.symmetric( horizontal: 5.w, ), itemCount: displayActivityCount, itemBuilder: (context, index) { final activity = activities[index]; return CustomerActivity( isOnlineCustomer: _isOnlineCustomer, activity: activity, ); }, ), if (totalActivity > 3) Padding( padding: EdgeInsets.symmetric( horizontal: 5.w, ), child: InkWell( onTap: () { context.pushNamed( AppRoute .customerActivitesScreen, extra: { "is_online_customer": _isOnlineCustomer, "activities": activities, }, ); }, borderRadius: BorderRadius.circular(2.5.w), child: Container( padding: EdgeInsets.symmetric( vertical: 1.5.h, ), decoration: BoxDecoration( border: Border.all( color: Colors.grey[300]!, ), borderRadius: BorderRadius.circular( 2.5.w, ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Lihat ${totalActivity - 3} Aktivitas Lainnya", style: TextStyle( color: Colors.grey[600], fontWeight: FontWeight.bold, fontSize: isTablet ? (AppFontSize.medium - 1.25) .sp : (AppFontSize.small - 1.25) .sp, ), ), SizedBox(width: 2.w), Icon( Icons .keyboard_arrow_down_rounded, color: Colors.grey[600], size: 5.w, ), ], ), ), ), ), ], ); }, error: (error, stackTrace) => _buildEmptyOrErrorActivities( Icons.error_rounded, "Ups, terjadi kesalahan", ), loading: () => const SizedBox(), ), if (!_isOnlineCustomer) ...[ SizedBox(height: 5.h), Padding( padding: EdgeInsets.symmetric(horizontal: 5.w), child: TextButton.icon( onPressed: () => _showDeleteConfirmation(context), style: TextButton.styleFrom( foregroundColor: Colors.red[700], padding: EdgeInsets.symmetric( vertical: 1.5.h, horizontal: 4.w, ), ), icon: Icon( Icons.delete_outline_rounded, size: 5.w, ), label: Text( "Hapus Pelanggan Ini", style: TextStyle( fontWeight: FontWeight.bold, fontSize: isTablet ? (AppFontSize.medium - 1.25).sp : (AppFontSize.small - 1.25).sp, ), ), ), ), ], SizedBox(height: 4.h), ], ), ), ), bottomNavigationBar: _isLoading ? null : Container( padding: EdgeInsets.all(5.w), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, -5), ), ], ), child: ElevatedButton( onPressed: _isOnlineCustomer || customerControllerState.isLoading ? null : _updateCustomer, style: ElevatedButton.styleFrom( backgroundColor: AppColor.primaryColor, minimumSize: Size(double.infinity, 6.5.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(2.5.w), ), disabledBackgroundColor: Colors.grey.shade300, ), child: Text( "Perbarui Informasi Pelanggan", style: TextStyle( fontWeight: FontWeight.bold, fontSize: AppFontSize.medium.sp, color: Colors.white, ), ), ), ), ), ); }, ); } Widget _buildSectionHeader(String title) { return Padding( padding: EdgeInsets.fromLTRB(5.w, 3.h, 5.w, 1.5.h), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( title, style: TextStyle( fontWeight: FontWeight.bold, fontSize: (AppFontSize.medium - 1.25).sp, ), ), ], ), ); } Widget _buildEmptyOrErrorAddress(IconData icon, String body) { return Container( height: 15.h, width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 5.w), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(2.5.w), border: Border.all( color: Colors.grey.shade200, style: BorderStyle.solid, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: Colors.grey[400], size: 8.w), SizedBox(height: 1.h), Text( body, style: TextStyle( fontSize: (AppFontSize.medium - 1.25).sp, color: Colors.grey[500], ), ), ], ), ); } Widget _buildEmptyOrErrorActivities(IconData icon, String body) { return Container( width: double.infinity, padding: EdgeInsets.symmetric(vertical: 4.h), margin: EdgeInsets.symmetric(horizontal: 5.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(2.5.w), border: Border.all(color: Colors.grey.shade100), ), child: Column( children: [ Icon(icon, color: Colors.grey[300], size: 10.w), SizedBox(height: 1.5.h), Text( body, style: TextStyle( fontSize: (AppFontSize.medium - 1.25).sp, color: Colors.grey[500], ), ), ], ), ); } }