import 'package:flutter/material.dart'; import 'package:flutter/gestures.dart'; import 'package:get/get.dart'; import '../controllers/warga_sewa_controller.dart'; import '../views/warga_layout.dart'; import '../../../services/navigation_service.dart'; import '../../../widgets/app_drawer.dart'; import '../../../theme/app_colors.dart'; import 'dart:async'; class WargaSewaView extends GetView { const WargaSewaView({super.key}); @override Widget build(BuildContext context) { final navigationService = Get.find(); return WargaLayout( drawer: AppDrawer( onNavItemTapped: controller.onNavItemTapped, onLogout: () { Get.find().toLogin(); }, ), appBar: AppBar( backgroundColor: AppColors.primary, elevation: 0, title: const Text( 'Sewa Aset Saya', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w600, ), ), centerTitle: true, bottom: PreferredSize( preferredSize: const Size.fromHeight(60), child: _buildTabBar(), ), ), body: Column( children: [ Expanded( child: TabBarView( controller: controller.tabController, physics: const PageScrollPhysics(), dragStartBehavior: DragStartBehavior.start, children: [ _buildBelumBayarTab(), _buildPendingTab(), _buildDiterimaTab(), _buildAktifTab(), _buildDikembalikanTab(), _buildSelesaiTab(), _buildDibatalkanTab(), ], ), ), ], ), floatingActionButton: Container( margin: const EdgeInsets.only(bottom: 16, right: 16), decoration: BoxDecoration( boxShadow: [ BoxShadow( color: AppColors.primary.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), spreadRadius: 0, ), ], borderRadius: BorderRadius.circular(16), ), child: FloatingActionButton.extended( onPressed: () => controller.navigateToRentals(), backgroundColor: AppColors.primary, elevation: 0, icon: const Icon( Icons.add_circle_outline, size: 20, color: Colors.white, ), label: const Text( 'Sewa Baru', style: TextStyle( fontWeight: FontWeight.w600, letterSpacing: 0.5, color: Colors.white, ), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); } Widget _buildTabBar() { return Container( width: double.infinity, height: 60, decoration: BoxDecoration( color: Colors.white, border: Border(bottom: BorderSide(color: Colors.grey.shade200)), ), child: TabBar( controller: controller.tabController, labelColor: AppColors.primary, unselectedLabelColor: Colors.grey.shade600, indicatorColor: AppColors.primary, indicatorWeight: 3, indicatorSize: TabBarIndicatorSize.label, labelPadding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 8), isScrollable: true, tabs: [ _buildTab(text: 'Belum Bayar', icon: Icons.payment_outlined), _buildTab(text: 'Pending', icon: Icons.pending_outlined), _buildTab(text: 'Diterima', icon: Icons.check_circle_outline), _buildTab(text: 'Aktif', icon: Icons.play_circle_outline), _buildTab(text: 'Dikembalikan', icon: Icons.assignment_return), _buildTab(text: 'Selesai', icon: Icons.task_alt_outlined), _buildTab(text: 'Dibatalkan', icon: Icons.cancel_outlined), ], ), ); } Widget _buildTab({required String text, required IconData icon}) { return Tab( height: 60, child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 20), const SizedBox(width: 8), Text( text, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), ), ], ), ); } Widget _buildPendingTab() { return Obx(() { // Show loading indicator while fetching data if (controller.isLoadingPending.value) { return const Center(child: CircularProgressIndicator()); } // Check if there is any data to display if (controller.pendingRentals.isNotEmpty) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: controller.pendingRentals .map( (rental) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: _buildUnpaidRentalCard(rental), ), ) .toList(), ), ); } // Return empty state if no data return _buildTabContent( icon: Icons.pending_actions, title: 'Tidak ada pembayaran yang sedang diperiksa', subtitle: 'Tidak ada sewa yang sedang dalam verifikasi pembayaran', buttonText: 'Sewa Sekarang', onButtonPressed: () => controller.navigateToRentals(), color: AppColors.warning, ); }); } Widget _buildAktifTab() { return Obx(() { if (controller.isLoadingActive.value) { return const Center(child: CircularProgressIndicator()); } if (controller.activeRentals.isEmpty) { return _buildTabContent( icon: Icons.play_circle_outline, title: 'Tidak ada sewa aktif', subtitle: 'Sewa yang sedang berlangsung akan muncul di sini', buttonText: 'Sewa Sekarang', onButtonPressed: () => controller.navigateToRentals(), color: Colors.blue, ); } return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: controller.activeRentals .map( (rental) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: _buildAktifRentalCard(rental), ), ) .toList(), ), ); }); } Widget _buildAktifRentalCard(Map rental) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColors.info.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(Icons.play_circle_fill, size: 18, color: AppColors.info), const SizedBox(width: 8), Text( rental['status'] ?? 'AKTIF', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.info, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( rental['imageUrl'] ?? '', width: 80, height: 80, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( width: 80, height: 80, color: Colors.grey[200], child: const Icon( Icons.broken_image, color: Colors.grey, ), ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.inventory_2_outlined, text: '${rental['jumlahUnit'] ?? 0} Unit', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.calendar_today_outlined, text: rental['tanggalSewa'] ?? '', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.schedule, text: rental['rentangWaktu'] ?? '', ), ], ), ), ], ), ), Divider(height: 1, thickness: 1, color: Colors.grey.shade100), // Price section Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), ], ), ], ), ), ], ), ); } Widget _buildBelumBayarTab() { return Obx(() { // Show loading indicator while fetching data if (controller.isLoading.value) { return const Center(child: CircularProgressIndicator()); } // Check if there is any data to display if (controller.rentals.isNotEmpty) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: [ // Build a card for each rental item ...controller.rentals .map( (rental) => Column( children: [ _buildUnpaidRentalCard(rental), const SizedBox(height: 20), ], ), ) .toList(), _buildTipsSection(), ], ), ); } // Return empty state if no data return _buildTabContent( icon: Icons.payment_outlined, title: 'Belum ada pembayaran', subtitle: 'Tidak ada sewa yang menunggu pembayaran', buttonText: 'Sewa Sekarang', onButtonPressed: () => controller.navigateToRentals(), color: AppColors.primary, ); }); } Widget _buildUnpaidRentalCard(Map rental) { // Determine status color based on status final bool isPembayaranDenda = rental['status'] == 'PEMBAYARAN DENDA'; final Color statusColor = isPembayaranDenda ? AppColors.error : AppColors.warning; return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon( isPembayaranDenda ? Icons.warning_amber_rounded : Icons.access_time_rounded, size: 18, color: statusColor, ), const SizedBox(width: 8), Text( rental['status'] ?? 'MENUNGGU PEMBAYARAN', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: statusColor, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Asset image with rounded corners ClipRRect( borderRadius: BorderRadius.circular(12), child: rental['imageUrl'] != null && rental['imageUrl'].toString().startsWith('http') ? Image.network( rental['imageUrl'], width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ) : Image.asset( rental['imageUrl'] ?? 'assets/images/gambar_pendukung.jpg', width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ), ), const SizedBox(width: 16), // Asset details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.inventory_2_outlined, text: '${rental['jumlahUnit'] ?? 0} Unit', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.calendar_today_outlined, text: rental['tanggalSewa'] ?? '', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.schedule, text: rental['rentangWaktu'] ?? '', ), const SizedBox(height: 12), if ((rental['status'] ?? '').toString().toUpperCase() == 'MENUNGGU PEMBAYARAN' && rental['updated_at'] != null) CountdownTimerWidget( updatedAt: DateTime.parse(rental['updated_at']), ), ], ), ), ], ), ), // Divider Divider(height: 1, thickness: 1, color: Colors.grey.shade100), // Price section Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Total Bayar', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), const SizedBox(height: 2), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), // Pay button ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( backgroundColor: rental['status'] == 'PEMBAYARAN DENDA' ? AppColors.error : AppColors.warning, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 10, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( rental['status'] == 'PEMBAYARAN DENDA' ? 'Bayar Denda' : 'Bayar Sekarang', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ], ), ), // Divider Divider(height: 1, thickness: 1, color: Colors.grey.shade100), // Action buttons Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), const SizedBox(width: 8), if ((rental['status'] ?? '').toString().toUpperCase() != 'PEMBAYARAN DENDA' && (rental['status'] ?? '').toString().toUpperCase() != 'PERIKSA PEMBAYARAN DENDA') Expanded( child: _buildActionButton( icon: Icons.cancel_outlined, label: 'Batalkan', onPressed: () => controller.cancelRental(rental['id']), iconColor: AppColors.error, ), ), ], ), ), ], ), ); } Widget _buildInfoRow({required IconData icon, required String text}) { return Row( children: [ Icon(icon, size: 14, color: AppColors.textSecondary), const SizedBox(width: 6), Text( text, style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), ], ); } Widget _buildActionButton({ required IconData icon, required String label, required VoidCallback onPressed, required Color iconColor, }) { return TextButton.icon( onPressed: onPressed, icon: Icon(icon, size: 16, color: iconColor), label: Text( label, style: TextStyle( color: AppColors.textPrimary, fontSize: 13, fontWeight: FontWeight.w500, ), ), style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 8), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ); } Widget _buildDiterimaTab() { return Obx(() { // Show loading indicator while fetching data if (controller.isLoadingAccepted.value) { return const Center(child: CircularProgressIndicator()); } // Check if there is any data to display if (controller.acceptedRentals.isNotEmpty) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: [ // Build a card for each accepted rental item ...controller.acceptedRentals .map( (rental) => Column( children: [ _buildDiterimaRentalCard(rental), const SizedBox(height: 20), ], ), ) .toList(), _buildTipsSectionDiterima(), ], ), ); } // Return empty state if no data return _buildTabContent( icon: Icons.check_circle_outline, title: 'Belum ada sewa diterima', subtitle: 'Sewa yang sudah diterima akan muncul di sini', buttonText: 'Sewa Sekarang', onButtonPressed: () => controller.navigateToRentals(), color: AppColors.success, ); }); } Widget _buildDiterimaRentalCard(Map rental) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColors.success.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon( Icons.check_circle_outline, size: 18, color: AppColors.success, ), const SizedBox(width: 8), Text( rental['status'] ?? 'DITERIMA', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.success, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Asset image with rounded corners ClipRRect( borderRadius: BorderRadius.circular(12), child: rental['imageUrl'] != null && rental['imageUrl'].toString().startsWith('http') ? Image.network( rental['imageUrl'], width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ) : Image.asset( rental['imageUrl'] ?? 'assets/images/gambar_pendukung.jpg', width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ), ), const SizedBox(width: 16), // Asset details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.inventory_2_outlined, text: '${rental['jumlahUnit'] ?? 0} Unit', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.calendar_today_outlined, text: rental['tanggalSewa'] ?? '', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.schedule, text: rental['rentangWaktu'] ?? '', ), ], ), ), ], ), ), // Divider Divider(height: 1, thickness: 1, color: Colors.grey.shade100), // Price section Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Total Bayar', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), const SizedBox(height: 2), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), ], ), ), // Divider Divider(height: 1, thickness: 1, color: Colors.grey.shade100), // Action buttons Padding( padding: const EdgeInsets.all(12), child: Row( children: [ Expanded( child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), const SizedBox(width: 8), Expanded( child: _buildActionButton( icon: Icons.cancel_outlined, label: 'Batalkan', onPressed: () => controller.cancelRental(rental['id']), iconColor: AppColors.error, ), ), ], ), ), ], ), ); } Widget _buildSelesaiTab() { return Obx(() { if (controller.isLoadingCompleted.value) { return const Center( child: Padding( padding: EdgeInsets.all(20.0), child: CircularProgressIndicator(), ), ); } if (controller.completedRentals.isEmpty) { return _buildTabContent( icon: Icons.check_circle_outline, title: 'Belum Ada Sewa Selesai', subtitle: 'Anda belum memiliki riwayat sewa yang telah selesai', buttonText: 'Lihat Aset', onButtonPressed: () => Get.toNamed('/warga-aset'), color: AppColors.info, ); } return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: controller.completedRentals .map( (rental) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: _buildSelesaiRentalCard(rental), ), ) .toList(), ), ); }); } Widget _buildSelesaiRentalCard(Map rental) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColors.info.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(Icons.task_alt_outlined, size: 18, color: AppColors.info), const SizedBox(width: 8), Text( rental['status'] ?? 'SELESAI', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.info, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Asset image with rounded corners ClipRRect( borderRadius: BorderRadius.circular(12), child: rental['imageUrl'] != null && rental['imageUrl'].toString().startsWith('http') ? Image.network( rental['imageUrl'], width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ) : Image.asset( rental['imageUrl'] ?? 'assets/images/gambar_pendukung.jpg', width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ), ), const SizedBox(width: 16), // Asset details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), const SizedBox(height: 4), Text( '${rental['jumlahUnit'] ?? 0} Unit', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.calendar_today, text: rental['rentangWaktu'] ?? '-', ), const SizedBox(height: 4), _buildInfoRow( icon: Icons.access_time, text: rental['duration'] ?? '-', ), ], ), ), ], ), ), // Divider Divider(color: Colors.grey.shade200, thickness: 1, height: 1), // Price section Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), ], ), ), ], ), ); } Widget _buildDibatalkanTab() { return Obx(() { if (controller.isLoadingCancelled.value) { return const Center( child: Padding( padding: EdgeInsets.all(20.0), child: CircularProgressIndicator(), ), ); } if (controller.cancelledRentals.isEmpty) { return _buildTabContent( icon: Icons.cancel_outlined, title: 'Belum Ada Sewa Dibatalkan', subtitle: 'Anda belum memiliki riwayat sewa yang dibatalkan', buttonText: 'Lihat Aset', onButtonPressed: () => Get.toNamed('/warga-aset'), color: AppColors.error, ); } return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: controller.cancelledRentals .map( (rental) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: _buildDibatalkanRentalCard(rental), ), ) .toList(), ), ); }); } Widget _buildDibatalkanRentalCard(Map rental) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColors.error.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(Icons.cancel_outlined, size: 18, color: AppColors.error), const SizedBox(width: 8), Text( rental['status'] ?? 'DIBATALKAN', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.error, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Asset image with rounded corners ClipRRect( borderRadius: BorderRadius.circular(12), child: rental['imageUrl'] != null && rental['imageUrl'].toString().startsWith('http') ? Image.network( rental['imageUrl'], width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ) : Image.asset( rental['imageUrl'] ?? 'assets/images/gambar_pendukung.jpg', width: 90, height: 90, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 90, height: 90, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.image_not_supported_outlined, size: 36, color: Colors.grey.shade400, ), ); }, ), ), const SizedBox(width: 16), // Asset details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), const SizedBox(height: 4), Text( '${rental['jumlahUnit'] ?? 0} Unit', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.calendar_today, text: rental['rentangWaktu'] ?? '-', ), const SizedBox(height: 4), _buildInfoRow( icon: Icons.access_time, text: rental['duration'] ?? '-', ), if (rental['alasanPembatalan'] != null && rental['alasanPembatalan'] != '-') Padding( padding: const EdgeInsets.only(top: 8.0), child: _buildInfoRow( icon: Icons.info_outline, text: 'Alasan: ${rental['alasanPembatalan']}', ), ), ], ), ), ], ), ), // Divider Divider(color: Colors.grey.shade200, thickness: 1, height: 1), // Price section Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), ], ), ], ), ), ], ), ); } Widget _buildTabContent({ required IconData icon, required String title, required String subtitle, required String buttonText, required VoidCallback onButtonPressed, required Color color, }) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), Container( padding: const EdgeInsets.all(28), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: color.withOpacity(0.08), blurRadius: 16, offset: const Offset(0, 4), spreadRadius: 2, ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: color.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon(icon, size: 60, color: color), ), const SizedBox(height: 28), Text( title, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( subtitle, style: TextStyle( fontSize: 16, color: AppColors.textSecondary, ), textAlign: TextAlign.center, ), const SizedBox(height: 36), SizedBox( width: double.infinity, height: 54, child: ElevatedButton( onPressed: onButtonPressed, style: ElevatedButton.styleFrom( backgroundColor: color, foregroundColor: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.add_circle_outline, size: 20), const SizedBox(width: 10), Text( buttonText, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ), ), const SizedBox(height: 24), // Tips section _buildTipsSection(), ], ), ), ); } Widget _buildTipsSection() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.08), blurRadius: 12, offset: const Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lightbulb_outline, color: AppColors.warning), const SizedBox(width: 8), Text( 'Tips & Informasi', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), const SizedBox(height: 12), _buildTipItem( icon: Icons.schedule, title: 'Pembayaran dalam 1 jam', description: 'Lakukan pembayaran dalam 1 jam untuk menghindari pembatalan otomatis.', ), ], ), ); } Widget _buildTipItem({ required IconData icon, required String title, required String description, }) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.info.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 18, color: AppColors.info), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 4), Text( description, style: TextStyle(fontSize: 13, color: AppColors.textSecondary), ), ], ), ), ], ); } Widget _buildTipsSectionDiterima() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.08), blurRadius: 12, offset: const Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lightbulb_outline, color: AppColors.warning), const SizedBox(width: 8), Text( 'Tips & Informasi', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), const SizedBox(height: 12), _buildTipItem( icon: Icons.access_time, title: 'Pengembalian Tepat Waktu', description: 'Lakukan pengembalian aset sebelum waktu sewa berakhir agar tidak dikenakan denda.', ), ], ), ); } Widget _buildDikembalikanTab() { return Obx(() { if (controller.isLoadingReturned.value) { return const Center( child: Padding( padding: EdgeInsets.all(20.0), child: CircularProgressIndicator(), ), ); } if (controller.returnedRentals.isEmpty) { return _buildTabContent( icon: Icons.assignment_return, title: 'Belum Ada Sewa Dikembalikan', subtitle: 'Sewa yang sudah dikembalikan akan muncul di sini', buttonText: 'Lihat Aset', onButtonPressed: () => Get.toNamed('/warga-aset'), color: Colors.deepPurple, ); } return SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( children: controller.returnedRentals .map( (rental) => Padding( padding: const EdgeInsets.only(bottom: 16.0), child: _buildDikembalikanRentalCard(rental), ), ) .toList(), ), ); }); } Widget _buildDikembalikanRentalCard(Map rental) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // Header section with status Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: AppColors.info.withOpacity(0.1), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(Icons.assignment_return, size: 18, color: AppColors.info), const SizedBox(width: 8), Text( rental['status'] ?? 'DIKEMBALIKAN', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: AppColors.info, ), ), ], ), ), // Asset details Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( rental['imageUrl'] ?? '', width: 64, height: 64, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( width: 64, height: 64, color: Colors.grey[200], child: const Icon(Icons.image, color: Colors.grey), ), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rental['name'] ?? 'Aset', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 8), _buildInfoRow( icon: Icons.inventory_2_outlined, text: '${rental['jumlahUnit'] ?? 0} Unit', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.calendar_today_outlined, text: rental['tanggalSewa'] ?? '', ), const SizedBox(height: 6), _buildInfoRow( icon: Icons.schedule, text: rental['rentangWaktu'] ?? '', ), ], ), ), ], ), ), Divider(height: 1, thickness: 1, color: Colors.grey.shade100), Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), Text( rental['totalPrice'] ?? 'Rp 0', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: _buildActionButton( icon: Icons.info_outline, label: 'Lihat Detail', onPressed: () => controller.viewRentalDetail(rental), iconColor: AppColors.info, ), ), ], ), ), ], ), ); } } class CountdownTimerWidget extends StatefulWidget { final DateTime updatedAt; final VoidCallback? onTimeout; const CountdownTimerWidget({ required this.updatedAt, this.onTimeout, Key? key, }) : super(key: key); @override State createState() => _CountdownTimerWidgetState(); } class _CountdownTimerWidgetState extends State { late Duration remaining; Timer? timer; @override void initState() { super.initState(); updateRemaining(); timer = Timer.periodic( const Duration(seconds: 1), (_) => updateRemaining(), ); print('DEBUG updated_at: ${widget.updatedAt}'); } void updateRemaining() { final now = DateTime.now(); final deadline = widget.updatedAt.add(const Duration(hours: 1)); setState(() { remaining = deadline.difference(now); if (remaining.isNegative) { remaining = Duration.zero; timer?.cancel(); widget.onTimeout?.call(); } }); } @override void dispose() { timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { if (remaining.inSeconds <= 0) { return Text('Waktu habis', style: TextStyle(color: Colors.red)); } final h = remaining.inHours; final m = remaining.inMinutes % 60; final s = remaining.inSeconds % 60; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: Colors.red.withOpacity(0.08), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.withOpacity(0.3), width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.timer_outlined, size: 14, color: Colors.red), const SizedBox(width: 4), Text( 'Bayar dalam ${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.red, ), ), ], ), ); } }