Files
bumrent_app/lib/app/modules/warga/views/warga_sewa_view.dart.bak
Andreas Malvino e7090af3da first commit
2025-06-02 22:39:03 +07:00

2851 lines
127 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
class WargaSewaView extends GetView<WargaSewaController> {
const WargaSewaView({super.key});
@override
Widget build(BuildContext context) {
final navigationService = Get.find<NavigationService>();
return WargaLayout(
drawer: AppDrawer(
onNavItemTapped: controller.onNavItemTapped,
onLogout: () {
Get.find<NavigationService>().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(),
_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.hourglass_empty_outlined),
_buildTab(text: 'Diterima', icon: Icons.check_circle_outline),
_buildTab(text: 'Aktif', icon: Icons.play_circle_outline),
_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 _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 _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: [
// Build a card for each pending rental item
...controller.pendingRentals.map((rental) => Column(
children: [
_buildPendingRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSectionPending(),
],
),
);
}
// Return empty state if no data
return _buildTabContent(
icon: Icons.hourglass_empty_outlined,
title: 'Tidak ada pembayaran pending',
subtitle: 'Pembayaran yang sedang diverifikasi akan muncul di sini',
buttonText: 'Sewa Sekarang',
onButtonPressed: () => controller.navigateToRentals(),
color: Colors.orange,
);
});
}
Widget _buildAktifTab() {
return Obx(() {
// Show loading indicator while fetching data
if (controller.isLoadingActive.value) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Check if there is any data to display
if (controller.activeRentals.isNotEmpty) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Build a card for each active rental item
...controller.activeRentals.map((rental) => Column(
children: [
_buildAktifRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSectionAktif(),
],
),
);
}
// Return empty state if no data
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,
);
});
}
Widget _buildPendingRentalCard(Map<String, dynamic> 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: Colors.orange.withOpacity(0.1),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(
Icons.hourglass_empty_outlined,
size: 18,
color: Colors.orange,
),
const SizedBox(width: 8),
Text(
rental['status'] ?? 'PERIKSA PEMBAYARAN',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
],
),
),
// 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,
),
),
const SizedBox(width: 16),
// Asset details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rental['name'] ?? 'Nama 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),
// Verification status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.orange.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.pending_outlined,
size: 14,
color: Colors.orange,
),
const SizedBox(width: 4),
const Text(
'Menunggu verifikasi',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.orange,
),
),
],
),
),
],
),
),
],
),
),
// 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,
),
),
],
),
// View detail button
ElevatedButton(
onPressed: () => controller.viewRentalDetail(rental),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Lihat Detail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
Widget _buildAktifRentalCard(Map<String, dynamic> 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: Colors.blue.withOpacity(0.1),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Icon(
Icons.play_circle_outline,
size: 18,
color: Colors.blue,
),
const SizedBox(width: 8),
Text(
rental['status'] ?? 'AKTIF',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
),
// 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,
),
),
const SizedBox(width: 16),
// Asset details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rental['name'] ?? 'Nama 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),
// Active status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.blue.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.access_time_filled,
size: 14,
color: Colors.blue,
),
const SizedBox(width: 4),
const Text(
'Sedang berlangsung',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
),
],
),
),
],
),
),
// 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,
),
),
],
),
// View detail button
ElevatedButton(
onPressed: () => controller.viewRentalDetail(rental),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Lihat Detail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
Widget _buildUnpaidRentalCard(Map<String, dynamic> 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),
// Countdown timer
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.error.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.timer_outlined,
size: 14,
color: AppColors.error,
),
const SizedBox(width: 4),
Text(
'Bayar dalam ${rental['countdown'] ?? '00:59:59'}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColors.error,
),
),
],
),
),
],
),
),
],
),
),
// 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),
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 _buildAktifTab() {
return Obx(() {
// Show loading indicator while fetching data
if (controller.isLoadingActive.value) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Check if there is any data to display
if (controller.activeRentals.isNotEmpty) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Build a card for each active rental item
...controller.activeRentals.map((rental) => Column(
children: [
_buildAktifRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSectionAktif(),
],
),
);
}
// Return empty state if no data
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,
);
});
}
Widget _buildDiterimaRentalCard(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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,
),
),
const SizedBox(width: 8),
Expanded(
child: _buildActionButton(
icon: Icons.refresh,
label: 'Pesan Kembali',
onPressed: () {},
iconColor: AppColors.success,
),
),
],
),
],
),
),
],
),
);
}
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 _buildTipsSectionPending() {
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: Colors.orange),
const SizedBox(width: 8),
Text(
'Tips Verifikasi',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
const SizedBox(height: 12),
_buildTipItem(
icon: Icons.access_time,
title: 'Proses Verifikasi',
description:
'Verifikasi pembayaran biasanya membutuhkan waktu 1-2 jam kerja.',
),
const SizedBox(height: 12),
_buildTipItem(
icon: Icons.image_outlined,
title: 'Bukti Pembayaran',
description:
'Pastikan bukti pembayaran yang diupload jelas dan terbaca.',
),
],
),
);
}
Widget _buildTipsSectionAktif() {
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: Colors.blue),
const SizedBox(width: 8),
Text(
'Tips Sewa Aktif',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
const SizedBox(height: 12),
_buildTipItem(
icon: Icons.rule_outlined,
title: 'Ketentuan Penggunaan',
description:
'Gunakan aset sesuai dengan ketentuan yang berlaku.',
),
const SizedBox(height: 12),
_buildTipItem(
icon: Icons.access_time,
title: 'Waktu Pengembalian',
description:
'Kembalikan aset tepat waktu untuk menghindari denda.',
),
],
),
);
}
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 _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: [
_buildAcceptedRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSection(),
],
),
);
}
// Return empty state if no data
return _buildTabContent(
icon: Icons.check_circle_outline,
title: 'Belum ada pembayaran diterima',
subtitle: 'Pembayaran yang telah diverifikasi akan muncul di sini',
buttonText: 'Sewa Sekarang',
onButtonPressed: () => controller.navigateToRentals(),
color: AppColors.success,
);
});
}
Widget _buildSelesaiTab() {
return Obx(() {
// Show loading indicator while fetching data
if (controller.isLoadingCompleted.value) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Check if there is any data to display
if (controller.completedRentals.isNotEmpty) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Build a card for each completed rental item
...controller.completedRentals.map((rental) => Column(
children: [
_buildCompletedRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSection(),
],
),
);
}
// Return empty state if no data
return _buildTabContent(
icon: Icons.task_alt_outlined,
title: 'Belum ada sewa selesai',
subtitle: 'Sewa yang telah selesai akan muncul di sini',
buttonText: 'Sewa Sekarang',
onButtonPressed: () => controller.navigateToRentals(),
color: AppColors.success,
);
});
}
Widget _buildDibatalkanTab() {
return Obx(() {
// Show loading indicator while fetching data
if (controller.isLoadingCancelled.value) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Check if there is any data to display
if (controller.cancelledRentals.isNotEmpty) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
children: [
// Build a card for each cancelled rental item
...controller.cancelledRentals.map((rental) => Column(
children: [
_buildCancelledRentalCard(rental),
const SizedBox(height: 20),
],
)).toList(),
_buildTipsSection(),
],
),
);
}
// Return empty state if no data
return _buildTabContent(
icon: Icons.cancel_outlined,
title: 'Tidak ada sewa dibatalkan',
subtitle: 'Sewa yang dibatalkan akan muncul di sini',
buttonText: 'Sewa Sekarang',
onButtonPressed: () => controller.navigateToRentals(),
color: AppColors.error,
);
});
}
Widget _buildAcceptedRentalCard(Map<String, dynamic> 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'] ?? 'PEMBAYARAN 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,
),
),
const SizedBox(width: 16),
// Asset details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rental['name'] ?? 'Nama 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),
// Payment accepted status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.success.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.success.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
size: 14,
color: AppColors.success,
),
const SizedBox(width: 4),
Text(
'Pembayaran diterima',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColors.success,
),
),
],
),
),
],
),
),
],
),
),
// Divider
Divider(height: 1, thickness: 1, color: Colors.grey.shade100),
// Action section
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Price
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Pembayaran',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 2),
Text(
rental['totalPrice'] ?? 'Rp 0',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
// View detail button
ElevatedButton(
onPressed: () => controller.viewRentalDetail(rental),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Lihat Detail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
Widget _buildCompletedRentalCard(Map<String, dynamic> 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.task_alt_outlined,
size: 18,
color: AppColors.success,
),
const SizedBox(width: 8),
Text(
rental['status'] ?? 'SELESAI',
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,
),
),
const SizedBox(width: 16),
// Asset details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rental['name'] ?? 'Nama 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),
// Completed status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.success.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.success.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
size: 14,
color: AppColors.success,
),
const SizedBox(width: 4),
Text(
'Sewa selesai',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColors.success,
),
),
],
),
),
],
),
),
],
),
),
// Divider
Divider(height: 1, thickness: 1, color: Colors.grey.shade100),
// Action section
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Price
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Pembayaran',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 2),
Text(
rental['totalPrice'] ?? 'Rp 0',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
// View detail button
ElevatedButton(
onPressed: () => controller.viewRentalDetail(rental),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Lihat Detail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}
Widget _buildCancelledRentalCard(Map<String, dynamic> 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,
),
),
const SizedBox(width: 16),
// Asset details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
rental['name'] ?? 'Nama 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),
// Cancelled status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.error.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.error.withOpacity(0.3),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.cancel,
size: 14,
color: AppColors.error,
),
const SizedBox(width: 4),
Text(
'Sewa dibatalkan',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: AppColors.error,
),
),
],
),
),
],
),
),
],
),
),
// Divider
Divider(height: 1, thickness: 1, color: Colors.grey.shade100),
// Action section
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Price
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Pembayaran',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 2),
Text(
rental['totalPrice'] ?? 'Rp 0',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
],
),
// View detail button
ElevatedButton(
onPressed: () => controller.viewRentalDetail(rental),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.error,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Lihat Detail',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
);
}