kelola penyewa dan beberapa error fix

This commit is contained in:
Andreas Malvino
2025-07-09 16:01:10 +07:00
parent 0423c2fdf9
commit 47766bbdda
90 changed files with 2705 additions and 1555 deletions

View File

@ -476,7 +476,7 @@ class ListPetugasMitraView extends GetView<ListPetugasMitraController> {
Get.snackbar(
'Error',
'Harap isi semua data',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);
@ -600,7 +600,7 @@ class ListPetugasMitraView extends GetView<ListPetugasMitraController> {
Get.snackbar(
'Error',
'Harap isi semua data',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red,
colorText: Colors.white,
);

View File

@ -203,7 +203,7 @@ class ListTagihanPeriodeView extends GetView<ListTagihanPeriodeController> {
backgroundColor: Colors.orange.withOpacity(0.1),
colorText: Colors.orange.shade800,
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
margin: const EdgeInsets.all(8),
);
},

View File

@ -81,11 +81,15 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// Navigate to PetugasTambahAsetView in add mode
// Navigate to PetugasTambahAsetView in add mode and refresh data when returning
Get.toNamed(
Routes.PETUGAS_TAMBAH_ASET,
arguments: {'isEditing': false, 'assetData': null},
);
)?.then((_) {
// Refresh data when returning from tambah_aset page
debugPrint('Returning from tambah aset page, refreshing data...');
controller.loadAsetData();
});
},
backgroundColor: AppColorsPetugas.babyBlueBright,
icon: Icon(Icons.add, color: AppColorsPetugas.blueGrotto),
@ -456,7 +460,7 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
Get.snackbar(
'Error',
'ID Aset tidak valid',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
return;
}
@ -467,7 +471,13 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
'isEditing': true,
'assetId': assetId,
},
);
)?.then((_) {
// Refresh data when returning from edit page
debugPrint(
'Returning from edit aset page, refreshing data...',
);
controller.loadAsetData();
});
},
child: Container(
padding: const EdgeInsets.all(5),
@ -670,235 +680,16 @@ class _PetugasAsetViewState extends State<PetugasAsetView>
}
}
void _showAddEditAssetDialog(
BuildContext context, {
Map<String, dynamic>? aset,
}) {
final isEditing = aset != null;
// In a real app, this would have proper form handling with controllers
showDialog(
context: context,
builder: (context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlue,
shape: BoxShape.circle,
),
child: Icon(
isEditing ? Icons.edit : Icons.add,
color: AppColorsPetugas.navyBlue,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isEditing ? 'Edit Aset' : 'Tambah Aset Baru',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColorsPetugas.navyBlue,
),
),
const SizedBox(height: 4),
Text(
'Silakan lengkapi form di bawah ini',
style: TextStyle(
fontSize: 14,
color: AppColorsPetugas.textSecondary,
),
),
],
),
),
],
),
const SizedBox(height: 24),
// Mock form - In a real app this would have actual form fields
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlueBright,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColorsPetugas.babyBlue),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Form pengelolaan aset akan ditampilkan di sini dengan field untuk:',
style: TextStyle(
fontSize: 14,
color: AppColorsPetugas.textPrimary,
),
),
const SizedBox(height: 16),
_buildMockFormField('Nama Aset', 'Contoh: Meja Rapat'),
_buildMockFormField('Kategori', 'Pilih kategori aset'),
_buildMockFormField(
'Harga',
'Masukkan harga per unit/periode',
),
_buildMockFormField(
'Satuan',
'Contoh: per hari, per bulan',
),
_buildMockFormField('Stok', 'Jumlah unit tersedia'),
_buildMockFormField(
'Deskripsi',
'Keterangan lengkap aset',
),
_buildMockToggle(
'Status Ketersediaan',
isEditing && aset?['tersedia'] == true,
),
],
),
),
const SizedBox(height: 24),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'Batal',
style: TextStyle(
color: AppColorsPetugas.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
// In a real app, we would save the form data
Get.snackbar(
isEditing ? 'Aset Diperbarui' : 'Aset Ditambahkan',
isEditing
? 'Aset berhasil diperbarui'
: 'Aset baru berhasil ditambahkan',
backgroundColor: AppColorsPetugas.success,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(16),
borderRadius: 10,
);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.blueGrotto,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(isEditing ? 'Simpan' : 'Tambah'),
),
],
),
],
),
),
);
},
);
}
Widget _buildMockFormField(String label, String hint) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColorsPetugas.textPrimary,
),
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColorsPetugas.babyBlue),
),
child: Row(
children: [
Expanded(
child: Text(
hint,
style: TextStyle(
fontSize: 14,
color: AppColorsPetugas.textLight,
),
),
),
Icon(
Icons.keyboard_arrow_down,
color: AppColorsPetugas.textSecondary,
size: 20,
),
],
),
),
],
),
);
}
Widget _buildMockToggle(String label, bool value) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: AppColorsPetugas.textPrimary,
),
),
Switch(
value: value,
onChanged: (_) {},
activeColor: AppColorsPetugas.blueGrotto,
),
],
),
);
void _showAddEditAssetDialog(BuildContext context) {
// Navigate to PetugasTambahAsetView in add mode and refresh data when returning
Get.toNamed(
Routes.PETUGAS_TAMBAH_ASET,
arguments: {'isEditing': false, 'assetData': null},
)?.then((_) {
// Refresh data when returning from tambah_aset page
debugPrint('Returning from tambah aset page, refreshing data...');
controller.loadAsetData();
});
}
void _showDeleteConfirmation(

View File

@ -1023,7 +1023,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status sewa telah diubah menjadi SELESAI',
backgroundColor: Colors.green,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -1054,7 +1054,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status sewa telah diubah menjadi DIKEMBALIKAN',
backgroundColor: Colors.teal,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -1127,7 +1127,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status sewa telah diubah menjadi DIBATALKAN',
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -1381,7 +1381,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status pengajuan diubah menjadi Periksa Pembayaran',
backgroundColor: Colors.amber.shade700,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
break;
@ -1394,7 +1394,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Pengajuan sewa aset telah disetujui',
backgroundColor: Colors.green.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
break;
@ -1407,7 +1407,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Permintaan pembayaran denda telah dikirim',
backgroundColor: Colors.deepOrange,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
break;
@ -1420,7 +1420,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status pengajuan diubah menjadi Periksa Denda',
backgroundColor: Colors.red.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
break;
@ -1433,7 +1433,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Aset telah dikembalikan dan sewa telah selesai',
backgroundColor: Colors.green.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
break;
@ -1476,7 +1476,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status sewa telah diubah menjadi DIBATALKAN',
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -1595,7 +1595,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Status sewa diubah menjadi Dikembalikan',
backgroundColor: Colors.teal,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
),
@ -1628,7 +1628,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Permintaan pembayaran denda telah dikirim',
backgroundColor: Colors.deepOrange,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
),
@ -1661,7 +1661,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Sewa aset telah selesai',
backgroundColor: Colors.purple,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
};
} else if (status == 'Dikembalikan') {
@ -1709,7 +1709,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Sewa aset telah selesai',
backgroundColor: Colors.purple,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
icon: const Icon(Icons.task_alt_outlined),
@ -1817,7 +1817,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Bukti sewa dalam format PDF sedang diunduh',
backgroundColor: AppColorsPetugas.blueGrotto,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
),
@ -1837,7 +1837,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Bukti sewa dalam format JPG sedang diunduh',
backgroundColor: AppColorsPetugas.blueGrotto,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
),
@ -1857,7 +1857,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Data sewa dalam format XLSX sedang diunduh',
backgroundColor: AppColorsPetugas.blueGrotto,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
),
@ -1963,7 +1963,8 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () {
nominalController.text = sewa.totalTagihan.toString();
nominalController.text =
sewa.totalTagihan.toInt().toString();
},
icon: Icon(
Icons.payments_outlined,
@ -2456,7 +2457,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Nominal denda harus diisi',
backgroundColor: Colors.red,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
return;
}
@ -2501,7 +2502,7 @@ class _PetugasDetailSewaViewState extends State<PetugasDetailSewaView> {
'Permintaan pembayaran denda telah dikirim',
backgroundColor: Colors.deepOrange,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
icon: const Icon(Icons.check_circle),

View File

@ -567,7 +567,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur tambah rekening bank sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -639,7 +639,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur edit rekening bank sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -689,7 +689,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur hapus rekening bank sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -765,7 +765,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur tambah mitra sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -845,7 +845,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur edit mitra sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(
@ -895,7 +895,7 @@ class PetugasManajemenBumdesView
Get.snackbar(
'Info',
'Fitur hapus mitra sedang dalam pengembangan',
snackPosition: SnackPosition.BOTTOM,
snackPosition: SnackPosition.TOP,
);
},
style: ElevatedButton.styleFrom(

View File

@ -55,11 +55,17 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed:
() => Get.toNamed(
Routes.PETUGAS_TAMBAH_PAKET,
arguments: {'isEditing': false},
),
onPressed: () async {
final result = await Get.toNamed(
Routes.PETUGAS_TAMBAH_PAKET,
arguments: {'isEditing': false},
);
// Refresh the package list if a package was added
if (result == true) {
controller.loadPaketData();
}
},
label: Text(
'Tambah Paket',
style: TextStyle(
@ -142,11 +148,17 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed:
() => Get.toNamed(
Routes.PETUGAS_TAMBAH_PAKET,
arguments: {'isEditing': false},
),
onPressed: () async {
final result = await Get.toNamed(
Routes.PETUGAS_TAMBAH_PAKET,
arguments: {'isEditing': false},
);
// Refresh the package list if a package was added
if (result == true) {
controller.loadPaketData();
}
},
icon: const Icon(Icons.add),
label: const Text('Tambah Paket'),
style: ElevatedButton.styleFrom(
@ -376,6 +388,21 @@ class PetugasPaketView extends GetView<PetugasPaketController> {
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () async {
final result = await Get.toNamed(
Routes.PETUGAS_TAMBAH_PAKET,
arguments: {
'isEditing': false,
'isViewing': true,
'paket': paket,
},
);
// Refresh the package list if data was modified
if (result == true) {
controller.loadPaketData();
}
},
child: Row(
children: [
// Paket image or icon

View File

@ -111,6 +111,27 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
),
),
),
actions: [
// Tambahkan indikator refresh
Obx(
() =>
controller.isRefreshing.value
? Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
),
),
)
: SizedBox.shrink(),
),
],
),
drawer: PetugasSideNavbar(controller: dashboardController),
drawerEdgeDragWidth: 60,
@ -148,7 +169,13 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
}
if (controller.filteredPenyewaList.isEmpty) {
return _buildEmptyState();
return RefreshIndicator(
onRefresh: () async {
await controller.refreshPenyewaList();
},
color: AppColorsPetugas.blueGrotto,
child: _buildEmptyStateWithScrollView(),
);
}
return _buildPenyewaList();
@ -282,208 +309,234 @@ class _PetugasPenyewaViewState extends State<PetugasPenyewaView>
);
}
// Widget untuk menampilkan empty state dengan ScrollView untuk mendukung RefreshIndicator
Widget _buildEmptyStateWithScrollView() {
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
SizedBox(
height: MediaQuery.of(Get.context!).size.height * 0.7,
child: _buildEmptyState(),
),
],
);
}
Widget _buildPenyewaList() {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.filteredPenyewaList.length,
itemBuilder: (context, index) {
final penyewa = controller.filteredPenyewaList[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(16),
child: InkWell(
return RefreshIndicator(
onRefresh: () async {
await controller.refreshPenyewaList();
},
color: AppColorsPetugas.blueGrotto,
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.filteredPenyewaList.length,
itemBuilder: (context, index) {
final penyewa = controller.filteredPenyewaList[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with avatar and badge
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlueLight.withOpacity(0.2),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(16),
child: InkWell(
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with avatar and badge
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlueLight.withOpacity(
0.2,
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Row(
children: [
// Avatar with border
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: 2,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: CircleAvatar(
radius: 24,
backgroundColor: AppColorsPetugas.babyBlueLight,
backgroundImage:
penyewa['avatar'] != null &&
penyewa['avatar']
.toString()
.isNotEmpty
? NetworkImage(penyewa['avatar'])
: null,
child:
penyewa['avatar'] == null ||
penyewa['avatar'].toString().isEmpty
? const Icon(
Icons.person,
color: Colors.white,
)
: null,
),
),
const SizedBox(width: 16),
// Name and email
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
penyewa['nama_lengkap'] ??
'Nama tidak tersedia',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColorsPetugas.navyBlue,
),
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.email_outlined,
size: 14,
color: Colors.grey[600],
),
const SizedBox(width: 4),
Expanded(
child: Text(
penyewa['email'] ??
'Email tidak tersedia',
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
_buildStatusBadge(penyewa['status']),
],
),
),
child: Row(
children: [
// Avatar with border
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: CircleAvatar(
radius: 24,
backgroundColor: AppColorsPetugas.babyBlueLight,
backgroundImage:
penyewa['avatar'] != null &&
penyewa['avatar']
.toString()
.isNotEmpty
? NetworkImage(penyewa['avatar'])
: null,
child:
penyewa['avatar'] == null ||
penyewa['avatar'].toString().isEmpty
? const Icon(
Icons.person,
color: Colors.white,
)
: null,
),
),
const SizedBox(width: 16),
// Name and email
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
penyewa['nama_lengkap'] ??
'Nama tidak tersedia',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColorsPetugas.navyBlue,
),
),
const SizedBox(height: 4),
Row(
children: [
Icon(
Icons.email_outlined,
size: 14,
color: Colors.grey[600],
),
const SizedBox(width: 4),
Expanded(
child: Text(
penyewa['email'] ??
'Email tidak tersedia',
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
overflow: TextOverflow.ellipsis,
),
),
],
),
],
),
),
_buildStatusBadge(penyewa['status']),
],
),
),
// Content section
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Show additional info only for Aktif and Ditangguhkan tabs
if (controller.currentTabIndex.value != 0) ...[
Row(
children: [
_buildInfoChip(
Icons.credit_card_outlined,
'NIK: ${penyewa['nik'] ?? 'Tidak tersedia'}',
),
const SizedBox(width: 8),
_buildInfoChip(
Icons.phone_outlined,
penyewa['no_hp'] ?? 'No. HP tidak tersedia',
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildInfoTile(
'Total Sewa',
penyewa['total_sewa']?.toString() ?? '0',
Icons.shopping_bag_outlined,
AppColorsPetugas.blueGrotto,
),
if (controller.currentTabIndex.value == 1 ||
controller.currentTabIndex.value == 2)
_buildActionChip(
label: 'Lihat Detail',
color: AppColorsPetugas.blueGrotto,
icon: Icons.visibility,
onTap: () {
Get.toNamed(
Routes.PETUGAS_DETAIL_PENYEWA,
arguments: {
'userId': penyewa['user_id'],
},
);
},
// Content section
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Show additional info only for Aktif and Ditangguhkan tabs
if (controller.currentTabIndex.value != 0) ...[
Row(
children: [
_buildInfoChip(
Icons.credit_card_outlined,
'NIK: ${penyewa['nik'] ?? 'Tidak tersedia'}',
),
],
),
],
const SizedBox(width: 8),
_buildInfoChip(
Icons.phone_outlined,
penyewa['no_hp'] ?? 'No. HP tidak tersedia',
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
_buildInfoTile(
'Total Sewa',
penyewa['total_sewa']?.toString() ?? '0',
Icons.shopping_bag_outlined,
AppColorsPetugas.blueGrotto,
),
if (controller.currentTabIndex.value == 1 ||
controller.currentTabIndex.value == 2)
_buildActionChip(
label: 'Lihat Detail',
color: AppColorsPetugas.blueGrotto,
icon: Icons.visibility,
onTap: () {
Get.toNamed(
Routes.PETUGAS_DETAIL_PENYEWA,
arguments: {
'userId': penyewa['user_id'],
},
);
},
),
],
),
],
// Add "Detail" button for Verifikasi tab
if (controller.currentTabIndex.value == 0) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: _buildActionChip(
label: 'Lihat Detail & Verifikasi',
color: AppColorsPetugas.blueGrotto,
icon: Icons.visibility,
onTap: () {
Get.toNamed(
Routes.PETUGAS_DETAIL_PENYEWA,
arguments: {
'userId': penyewa['user_id'],
},
);
},
// Add "Detail" button for Verifikasi tab
if (controller.currentTabIndex.value == 0) ...[
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: _buildActionChip(
label: 'Lihat Detail & Verifikasi',
color: AppColorsPetugas.blueGrotto,
icon: Icons.visibility,
onTap: () {
Get.toNamed(
Routes.PETUGAS_DETAIL_PENYEWA,
arguments: {
'userId': penyewa['user_id'],
},
);
},
),
),
),
],
),
],
),
],
],
],
),
),
),
],
],
),
),
),
),
),
);
},
);
},
),
);
}

View File

@ -15,7 +15,9 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
appBar: AppBar(
title: Obx(
() => Text(
controller.isEditing.value ? 'Edit Paket' : 'Tambah Paket',
controller.isViewing.value
? 'Detail Paket'
: (controller.isEditing.value ? 'Edit Paket' : 'Tambah Paket'),
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
@ -168,15 +170,16 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
'Item Paket',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
ElevatedButton.icon(
onPressed: () => _showAddItemDialog(),
icon: const Icon(Icons.add),
label: const Text('Tambah Item'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.babyBlueLight,
foregroundColor: AppColorsPetugas.blueGrotto,
if (!controller.isViewing.value)
ElevatedButton.icon(
onPressed: () => _showAddItemDialog(),
icon: const Icon(Icons.add),
label: const Text('Tambah Item'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.babyBlueLight,
foregroundColor: AppColorsPetugas.blueGrotto,
),
),
),
],
),
const SizedBox(height: 16),
@ -208,26 +211,33 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
Text('Stok tersedia: ${item['stok']}'),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => _showEditItemDialog(index),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed:
() => controller.removeItem(index),
),
],
),
trailing:
controller.isViewing.value
? null
: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed:
() =>
_showEditItemDialog(index),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed:
() => controller.removeItem(
index,
),
),
],
),
),
);
},
@ -526,6 +536,9 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
String? prefixText,
IconData? prefixIcon,
}) {
final petugasController =
this.controller; // Reference to PetugasTambahPaketController
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -539,7 +552,7 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
color: AppColorsPetugas.textPrimary,
),
),
if (isRequired) ...[
if (isRequired && !petugasController.isViewing.value) ...[
const SizedBox(width: 4),
Text(
'*',
@ -553,43 +566,55 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
],
),
const SizedBox(height: 8),
TextFormField(
controller: controller,
maxLines: maxLines,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
filled: true,
fillColor: AppColorsPetugas.babyBlueBright,
prefixText: prefixText,
prefixIcon:
prefixIcon != null
? Icon(
prefixIcon,
size: 20,
color: AppColorsPetugas.textSecondary,
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
Obx(
() => TextFormField(
controller: controller,
maxLines: maxLines,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
readOnly: petugasController.isViewing.value,
style: TextStyle(
color:
petugasController.isViewing.value
? AppColorsPetugas.textSecondary
: AppColorsPetugas.textPrimary,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: AppColorsPetugas.blueGrotto,
width: 1.5,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
filled: true,
fillColor:
petugasController.isViewing.value
? AppColorsPetugas.babyBlueLight
: AppColorsPetugas.babyBlueBright,
prefixText: prefixText,
prefixIcon:
prefixIcon != null
? Icon(
prefixIcon,
size: 20,
color: AppColorsPetugas.textSecondary,
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: AppColorsPetugas.blueGrotto,
width: 1.5,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
),
),
@ -646,7 +671,10 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
vertical: 12,
),
filled: true,
fillColor: AppColorsPetugas.babyBlueBright,
fillColor:
controller.isViewing.value
? AppColorsPetugas.babyBlueLight
: AppColorsPetugas.babyBlueBright,
),
items:
options.map((option) {
@ -661,9 +689,12 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
),
);
}).toList(),
onChanged: (value) {
if (value != null) onChanged(value);
},
onChanged:
controller.isViewing.value
? null // Disable in view-only mode
: (value) {
if (value != null) onChanged(value);
},
icon: Icon(
Icons.keyboard_arrow_down_rounded,
color: AppColorsPetugas.blueGrotto,
@ -717,41 +748,42 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
runSpacing: 12,
children: [
// Add button
GestureDetector(
onTap: _showImageSourceOptions,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlueBright,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColorsPetugas.babyBlue,
width: 1,
style: BorderStyle.solid,
if (!controller.isViewing.value)
GestureDetector(
onTap: _showImageSourceOptions,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: AppColorsPetugas.babyBlueBright,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: AppColorsPetugas.babyBlue,
width: 1,
style: BorderStyle.solid,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.add_photo_alternate_outlined,
color: AppColorsPetugas.blueGrotto,
size: 32,
),
const SizedBox(height: 8),
Text(
'Tambah Foto',
style: TextStyle(
fontSize: 12,
color: AppColorsPetugas.blueGrotto,
fontWeight: FontWeight.w500,
),
),
],
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.add_photo_alternate_outlined,
color: AppColorsPetugas.blueGrotto,
size: 32,
),
const SizedBox(height: 8),
Text(
'Tambah Foto',
style: TextStyle(
fontSize: 12,
color: AppColorsPetugas.blueGrotto,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
// Image previews
...List<Widget>.generate(controller.selectedImages.length, (
index,
@ -811,21 +843,24 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
Positioned(
top: 4,
right: 4,
child: InkWell(
onTap: () => controller.removeImage(index),
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
size: 18,
color: Colors.red,
),
),
),
child:
!controller.isViewing.value
? InkWell(
onTap: () => controller.removeImage(index),
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: const Icon(
Icons.close,
size: 18,
color: Colors.red,
),
),
)
: const SizedBox.shrink(),
),
],
);
@ -949,67 +984,97 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
),
],
),
child: Row(
children: [
OutlinedButton.icon(
onPressed: () => Get.back(),
icon: const Icon(Icons.arrow_back),
label: const Text('Batal'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColorsPetugas.textSecondary,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
side: BorderSide(color: AppColorsPetugas.divider),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
child: Obx(() {
// In view-only mode, just show a back button
if (controller.isViewing.value) {
return SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () => Get.back(),
icon: const Icon(Icons.arrow_back),
label: const Text('Kembali'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.blueGrotto,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Obx(() {
final isValid = controller.isFormValid.value;
final isSubmitting = controller.isSubmitting.value;
return ElevatedButton.icon(
onPressed:
controller.isFormChanged.value && !isSubmitting
? controller.savePaket
: null,
icon:
);
}
// For edit/add mode, show cancel and save buttons
return Row(
children: [
OutlinedButton.icon(
onPressed: () => Get.back(),
icon: const Icon(Icons.arrow_back),
label: const Text('Batal'),
style: OutlinedButton.styleFrom(
foregroundColor: AppColorsPetugas.textSecondary,
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
side: BorderSide(color: AppColorsPetugas.divider),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Obx(() {
final isValid = controller.isFormValid.value;
final isSubmitting = controller.isSubmitting.value;
return ElevatedButton.icon(
onPressed:
controller.isFormChanged.value && !isSubmitting
? controller.savePaket
: null,
icon:
isSubmitting
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.save),
label: Text(
isSubmitting
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Icon(Icons.save),
label: Text(
isSubmitting
? 'Menyimpan...'
: (controller.isEditing.value
? 'Simpan Paket'
: 'Tambah Paket'),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.blueGrotto,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
? 'Menyimpan...'
: (controller.isEditing.value
? 'Simpan Paket'
: 'Tambah Paket'),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
style: ElevatedButton.styleFrom(
backgroundColor: AppColorsPetugas.blueGrotto,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
disabledBackgroundColor: AppColorsPetugas.textLight,
),
disabledBackgroundColor: AppColorsPetugas.textLight,
),
);
}),
),
],
),
);
}),
),
],
);
}),
);
}
@ -1035,7 +1100,10 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
() => Material(
color: Colors.transparent,
child: InkWell(
onTap: () => controller.toggleTimeOption(option),
onTap:
controller.isViewing.value
? null
: () => controller.toggleTimeOption(option),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(
@ -1173,12 +1241,22 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
controller: priceController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
readOnly: controller.isViewing.value,
style: TextStyle(
color:
controller.isViewing.value
? AppColorsPetugas.textSecondary
: AppColorsPetugas.textPrimary,
),
decoration: InputDecoration(
hintText: 'Masukkan harga',
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
prefixText: 'Rp ',
filled: true,
fillColor: AppColorsPetugas.babyBlueBright,
fillColor:
controller.isViewing.value
? AppColorsPetugas.babyBlueLight
: AppColorsPetugas.babyBlueBright,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
@ -1210,11 +1288,21 @@ class PetugasTambahPaketView extends GetView<PetugasTambahPaketController> {
controller: maxController,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
readOnly: controller.isViewing.value,
style: TextStyle(
color:
controller.isViewing.value
? AppColorsPetugas.textSecondary
: AppColorsPetugas.textPrimary,
),
decoration: InputDecoration(
hintText: 'Opsional',
hintStyle: TextStyle(color: AppColorsPetugas.textLight),
filled: true,
fillColor: AppColorsPetugas.babyBlueBright,
fillColor:
controller.isViewing.value
? AppColorsPetugas.babyBlueLight
: AppColorsPetugas.babyBlueBright,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,