diff --git a/lib/app/data/models/donatur_model.dart b/lib/app/data/models/donatur_model.dart index e80c20e..a931f0e 100644 --- a/lib/app/data/models/donatur_model.dart +++ b/lib/app/data/models/donatur_model.dart @@ -5,6 +5,7 @@ class DonaturModel { final String? nama; final String? alamat; final String? telepon; + final String? noHp; final String? email; final String? jenis; final String? deskripsi; @@ -17,6 +18,7 @@ class DonaturModel { this.nama, this.alamat, this.telepon, + this.noHp, this.email, this.jenis, this.deskripsi, @@ -35,6 +37,7 @@ class DonaturModel { nama: json["nama"], alamat: json["alamat"], telepon: json["telepon"], + noHp: json["no_hp"] ?? json["telepon"], email: json["email"], jenis: json["jenis"], deskripsi: json["deskripsi"], @@ -52,6 +55,7 @@ class DonaturModel { "nama": nama, "alamat": alamat, "telepon": telepon, + "no_hp": noHp ?? telepon, "email": email, "jenis": jenis, "deskripsi": deskripsi, diff --git a/lib/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart b/lib/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart index 1c7bfc3..e36ad3d 100644 --- a/lib/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart +++ b/lib/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart @@ -21,6 +21,14 @@ class PenitipanBantuanController extends GetxController { // Path untuk bukti serah terima final Rx fotoBuktiSerahTerimaPath = Rx(null); + // Path untuk foto bantuan + final RxList fotoBantuanPaths = [].obs; + + // Untuk pencarian donatur + final RxList hasilPencarianDonatur = [].obs; + final RxBool isSearchingDonatur = false.obs; + final TextEditingController donaturSearchController = TextEditingController(); + // Indeks kategori yang dipilih untuk filter final RxInt selectedCategoryIndex = 0.obs; @@ -70,6 +78,7 @@ class PenitipanBantuanController extends GetxController { @override void onClose() { searchController.dispose(); + donaturSearchController.dispose(); super.onClose(); } @@ -196,6 +205,92 @@ class PenitipanBantuanController extends GetxController { } } + Future pickFotoBantuan({bool fromCamera = true}) async { + try { + final pickedFile = await _imagePicker.pickImage( + source: fromCamera ? ImageSource.camera : ImageSource.gallery, + imageQuality: 70, + maxWidth: 1000, + ); + + if (pickedFile != null) { + fotoBantuanPaths.add(pickedFile.path); + } + } catch (e) { + print('Error picking image: $e'); + Get.snackbar( + 'Error', + 'Gagal mengambil gambar: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } + + void removeFotoBantuan(int index) { + if (index >= 0 && index < fotoBantuanPaths.length) { + fotoBantuanPaths.removeAt(index); + } + } + + Future tambahPenitipanBantuan({ + required String stokBantuanId, + required double jumlah, + required String deskripsi, + String? donaturId, + bool isUang = false, + }) async { + if (fotoBantuanPaths.isEmpty) { + Get.snackbar( + 'Error', + 'Foto bantuan harus diupload', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + isLoading.value = true; + isUploading.value = true; + try { + await _supabaseService.tambahPenitipanBantuan( + stokBantuanId: stokBantuanId, + jumlah: jumlah, + deskripsi: deskripsi, + fotoBantuanPaths: fotoBantuanPaths, + donaturId: donaturId, + isUang: isUang, + ); + + // Reset paths setelah berhasil + fotoBantuanPaths.clear(); + + await loadPenitipanData(); + Get.back(); // Tutup dialog + Get.snackbar( + 'Sukses', + 'Penitipan bantuan berhasil ditambahkan', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + print('Error adding penitipan: $e'); + Get.snackbar( + 'Error', + 'Gagal menambahkan penitipan: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } finally { + isLoading.value = false; + isUploading.value = false; + } + } + Future verifikasiPenitipan(String penitipanId) async { if (fotoBuktiSerahTerimaPath.value == null) { Get.snackbar( @@ -318,13 +413,8 @@ class PenitipanBantuanController extends GetxController { } Future refreshData() async { - isLoading.value = true; - try { - await loadPenitipanData(); - await loadKategoriBantuanData(); - } finally { - isLoading.value = false; - } + await loadPenitipanData(); + await loadKategoriBantuanData(); } void changeCategory(int index) { @@ -485,4 +575,79 @@ class PenitipanBantuanController extends GetxController { print('Error saat memuat ulang data petugas desa: $e'); } } + + Future searchDonatur(String keyword) async { + if (keyword.length < 3) { + hasilPencarianDonatur.clear(); + return; + } + + isSearchingDonatur.value = true; + try { + final result = await _supabaseService.searchDonatur(keyword); + if (result != null) { + hasilPencarianDonatur.value = + result.map((data) => DonaturModel.fromJson(data)).toList(); + } else { + hasilPencarianDonatur.clear(); + } + } catch (e) { + print('Error searching donatur: $e'); + hasilPencarianDonatur.clear(); + } finally { + isSearchingDonatur.value = false; + } + } + + // Metode untuk mendapatkan daftar donatur + Future> getDaftarDonatur() async { + try { + final result = await _supabaseService.getDaftarDonatur(); + if (result != null) { + return result.map((data) => DonaturModel.fromJson(data)).toList(); + } + return []; + } catch (e) { + print('Error getting daftar donatur: $e'); + return []; + } + } + + Future tambahDonatur({ + required String nama, + required String noHp, + String? alamat, + String? email, + }) async { + try { + final donaturData = { + 'nama': nama, + 'no_hp': noHp, + 'alamat': alamat, + 'email': email, + 'created_at': DateTime.now().toIso8601String(), + 'updated_at': DateTime.now().toIso8601String(), + }; + + return await _supabaseService.tambahDonatur(donaturData); + } catch (e) { + print('Error adding donatur: $e'); + Get.snackbar( + 'Error', + 'Gagal menambahkan donatur: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return null; + } + } + + // Mendapatkan informasi apakah stok bantuan berupa uang + bool isStokBantuanUang(String stokBantuanId) { + if (!stokBantuanMap.containsKey(stokBantuanId)) { + return false; + } + return stokBantuanMap[stokBantuanId]?.isUang ?? false; + } } diff --git a/lib/app/modules/petugas_desa/views/penitipan_view.dart b/lib/app/modules/petugas_desa/views/penitipan_view.dart index 9b81fe3..d89ee1f 100644 --- a/lib/app/modules/petugas_desa/views/penitipan_view.dart +++ b/lib/app/modules/petugas_desa/views/penitipan_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:penyaluran_app/app/data/models/donatur_model.dart'; import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart'; import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart'; import 'package:penyaluran_app/app/theme/app_theme.dart'; @@ -39,6 +40,11 @@ class PenitipanView extends GetView { ), ), )), + floatingActionButton: FloatingActionButton( + onPressed: () => _showTambahPenitipanDialog(context), + backgroundColor: AppTheme.primaryColor, + child: const Icon(Icons.add), + ), ); } @@ -983,4 +989,723 @@ class PenitipanView extends GetView { ], ); } + + void _showTambahPenitipanDialog(BuildContext context) { + final formKey = GlobalKey(); + final TextEditingController jumlahController = TextEditingController(); + final TextEditingController deskripsiController = TextEditingController(); + + // Variabel untuk menyimpan nilai yang dipilih + final Rx selectedStokBantuanId = Rx(null); + final Rx selectedDonaturId = Rx(null); + final Rx selectedDonatur = Rx(null); + + // Reset foto bantuan paths + controller.fotoBantuanPaths.clear(); + controller.donaturSearchController.clear(); + controller.hasilPencarianDonatur.clear(); + + Get.dialog( + Dialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Obx(() { + // Dapatkan informasi apakah stok bantuan berupa uang + bool isUang = false; + String satuan = ''; + if (selectedStokBantuanId.value != null) { + isUang = + controller.isStokBantuanUang(selectedStokBantuanId.value!); + satuan = + controller.getKategoriSatuan(selectedStokBantuanId.value); + } + + return Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tambah Penitipan Bantuan', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + // Pilih kategori bantuan + Text( + 'Kategori Bantuan', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + DropdownButtonFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + hint: const Text('Pilih kategori bantuan'), + value: selectedStokBantuanId.value, + items: controller.stokBantuanMap.entries.map((entry) { + return DropdownMenuItem( + value: entry.key, + child: Text(entry.value.nama ?? 'Tidak ada nama'), + ); + }).toList(), + onChanged: (value) { + selectedStokBantuanId.value = value; + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Kategori bantuan harus dipilih'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Jumlah bantuan + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isUang ? 'Jumlah Uang (Rp)' : 'Jumlah Bantuan', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: jumlahController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: + isUang ? 'Contoh: 100000' : 'Contoh: 10', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Jumlah harus diisi'; + } + if (double.tryParse(value) == null) { + return 'Jumlah harus berupa angka'; + } + if (double.parse(value) <= 0) { + return 'Jumlah harus lebih dari 0'; + } + return null; + }, + ), + ], + ), + ), + if (satuan.isNotEmpty && !isUang) ...[ + const SizedBox(width: 8), + Container( + margin: const EdgeInsets.only(top: 32), + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 12), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade400), + ), + child: Text( + satuan, + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ], + ), + const SizedBox(height: 16), + + // Donatur (wajib) + Text( + 'Donatur', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (selectedDonatur.value != null) ...[ + // Tampilkan donatur yang dipilih + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + selectedDonatur.value!.nama ?? + 'Tidak ada nama', + style: const TextStyle( + fontWeight: FontWeight.bold), + ), + if (selectedDonatur.value!.noHp != null) + Text(selectedDonatur.value!.noHp!), + ], + ), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () { + selectedDonatur.value = null; + selectedDonaturId.value = null; + }, + ), + ], + ), + ), + ] else ...[ + // Tampilkan pencarian donatur + TextFormField( + controller: controller.donaturSearchController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Cari donatur (min. 3 karakter)', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + suffixIcon: controller.isSearchingDonatur.value + ? const Padding( + padding: EdgeInsets.all(8.0), + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : const Icon(Icons.search), + ), + onChanged: (value) { + controller.searchDonatur(value); + }, + validator: (value) { + if (selectedDonaturId.value == null) { + return 'Donatur harus dipilih'; + } + return null; + }, + ), + + // Hasil pencarian donatur + if (controller.hasilPencarianDonatur.isNotEmpty) + Container( + margin: const EdgeInsets.only(top: 8), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + constraints: const BoxConstraints(maxHeight: 150), + child: ListView.builder( + shrinkWrap: true, + itemCount: + controller.hasilPencarianDonatur.length, + itemBuilder: (context, index) { + final donatur = + controller.hasilPencarianDonatur[index]; + return ListTile( + title: + Text(donatur.nama ?? 'Tidak ada nama'), + subtitle: donatur.noHp != null + ? Text(donatur.noHp!) + : null, + dense: true, + onTap: () { + selectedDonatur.value = donatur; + selectedDonaturId.value = donatur.id; + controller.donaturSearchController + .clear(); + controller.hasilPencarianDonatur.clear(); + }, + ); + }, + ), + ), + + // Tombol tambah donatur baru + if (controller.donaturSearchController.text.length >= + 3 && + controller.hasilPencarianDonatur.isEmpty && + !controller.isSearchingDonatur.value) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: OutlinedButton.icon( + onPressed: () { + _showTambahDonaturDialog(context, + (donaturId) { + // Callback ketika donatur berhasil ditambahkan + controller + .getDonaturInfo(donaturId) + .then((donatur) { + if (donatur != null) { + selectedDonatur.value = donatur; + selectedDonaturId.value = donatur.id; + } + }); + }); + }, + icon: const Icon(Icons.add), + label: const Text('Tambah Donatur Baru'), + style: OutlinedButton.styleFrom( + foregroundColor: AppTheme.primaryColor, + ), + ), + ), + ], + ], + ), + const SizedBox(height: 16), + + // Deskripsi + Text( + 'Deskripsi', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: deskripsiController, + maxLines: 3, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Deskripsi bantuan', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Deskripsi harus diisi'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Upload foto bantuan + Text( + 'Foto Bantuan', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + if (controller.fotoBantuanPaths.isEmpty) + InkWell( + onTap: () => _showPilihSumberFoto(context), + child: Container( + height: 150, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade400), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.camera_alt, + size: 48, + color: Colors.grey.shade600, + ), + const SizedBox(height: 8), + Text( + 'Tambah Foto', + style: TextStyle( + color: Colors.grey.shade600, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ) + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 100, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.fotoBantuanPaths.length + + 1, // +1 untuk tombol tambah + itemBuilder: (context, index) { + if (index == + controller.fotoBantuanPaths.length) { + // Tombol tambah foto + return InkWell( + onTap: () => _showPilihSumberFoto(context), + child: Container( + width: 100, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey.shade400), + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.add_photo_alternate, + size: 32, + color: Colors.grey.shade600, + ), + const SizedBox(height: 4), + Text( + 'Tambah', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 12, + ), + ), + ], + ), + ), + ); + } + + // Tampilkan foto yang sudah diambil + return Stack( + children: [ + Container( + width: 100, + height: 100, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + image: FileImage(File(controller + .fotoBantuanPaths[index])), + fit: BoxFit.cover, + ), + ), + ), + Positioned( + top: 4, + right: 12, + child: GestureDetector( + onTap: () => + controller.removeFotoBantuan(index), + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: + Colors.black.withOpacity(0.5), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.close, + color: Colors.white, + size: 16, + ), + ), + ), + ), + ], + ); + }, + ), + ), + ], + ), + + const SizedBox(height: 24), + + // Tombol aksi + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: controller.isUploading.value + ? null + : () { + if (formKey.currentState!.validate()) { + if (controller.fotoBantuanPaths.isEmpty) { + Get.snackbar( + 'Error', + 'Foto bantuan harus diupload', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + controller.tambahPenitipanBantuan( + stokBantuanId: + selectedStokBantuanId.value!, + jumlah: + double.parse(jumlahController.text), + deskripsi: deskripsiController.text, + donaturId: selectedDonaturId.value, + isUang: isUang, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + ), + child: controller.isUploading.value + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Text('Simpan'), + ), + ], + ), + ], + ), + ), + ); + }), + ), + ), + ); + } + + void _showPilihSumberFoto(BuildContext context) { + Get.bottomSheet( + Container( + padding: const EdgeInsets.all(16), + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Pilih Sumber Foto', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + ListTile( + leading: const Icon(Icons.camera_alt), + title: const Text('Kamera'), + onTap: () { + Get.back(); + controller.pickFotoBantuan(fromCamera: true); + }, + ), + ListTile( + leading: const Icon(Icons.photo_library), + title: const Text('Galeri'), + onTap: () { + Get.back(); + controller.pickFotoBantuan(fromCamera: false); + }, + ), + ], + ), + ), + ); + } + + void _showTambahDonaturDialog( + BuildContext context, Function(String) onDonaturAdded) { + final formKey = GlobalKey(); + final TextEditingController namaController = TextEditingController(); + final TextEditingController noHpController = TextEditingController(); + final TextEditingController alamatController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + + Get.dialog( + Dialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Tambah Donatur Baru', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + + // Nama donatur + Text( + 'Nama Donatur', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: namaController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Masukkan nama donatur', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nama donatur harus diisi'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // No HP + Text( + 'Nomor HP', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: noHpController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Masukkan nomor HP', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nomor HP harus diisi'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Alamat (opsional) + Text( + 'Alamat (Opsional)', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: alamatController, + maxLines: 2, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Masukkan alamat', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + const SizedBox(height: 16), + + // Email (opsional) + Text( + 'Email (Opsional)', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 8), + TextFormField( + controller: emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + hintText: 'Masukkan email', + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + ), + ), + const SizedBox(height: 24), + + // Tombol aksi + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Batal'), + ), + const SizedBox(width: 8), + ElevatedButton( + onPressed: () async { + if (formKey.currentState!.validate()) { + final donaturId = await controller.tambahDonatur( + nama: namaController.text, + noHp: noHpController.text, + alamat: alamatController.text.isEmpty + ? null + : alamatController.text, + email: emailController.text.isEmpty + ? null + : emailController.text, + ); + + if (donaturId != null) { + Get.back(); + onDonaturAdded(donaturId); + Get.snackbar( + 'Sukses', + 'Donatur berhasil ditambahkan', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.primaryColor, + ), + child: const Text('Simpan'), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } } diff --git a/lib/app/services/supabase_service.dart b/lib/app/services/supabase_service.dart index edec2a0..074e874 100644 --- a/lib/app/services/supabase_service.dart +++ b/lib/app/services/supabase_service.dart @@ -551,6 +551,57 @@ class SupabaseService extends GetxService { } } + // Metode untuk menambahkan penitipan bantuan baru + Future tambahPenitipanBantuan({ + required String stokBantuanId, + required double jumlah, + required String deskripsi, + required List fotoBantuanPaths, + String? donaturId, + bool isUang = false, + }) async { + try { + final petugasDesaId = client.auth.currentUser?.id; + if (petugasDesaId == null) { + throw 'User tidak ditemukan'; + } + + // Upload foto bantuan + final fotoBantuanUrls = await uploadMultipleFiles( + fotoBantuanPaths, 'bantuan', 'foto_bantuan'); + + if (fotoBantuanUrls == null || fotoBantuanUrls.isEmpty) { + throw 'Gagal mengupload foto bantuan'; + } + + // Data penitipan + final penitipanData = { + 'stok_bantuan_id': stokBantuanId, + 'jumlah': jumlah, + 'deskripsi': deskripsi, + 'status': + 'TERVERIFIKASI', // Langsung terverifikasi karena diinput oleh petugas desa + 'foto_bantuan': fotoBantuanUrls, + 'tanggal_penitipan': DateTime.now().toIso8601String(), + 'tanggal_verifikasi': DateTime.now().toIso8601String(), + 'created_at': DateTime.now().toIso8601String(), + 'updated_at': DateTime.now().toIso8601String(), + 'petugas_desa_id': petugasDesaId, + 'is_uang': isUang, + }; + + // Tambahkan donatur_id jika ada + if (donaturId != null && donaturId.isNotEmpty) { + penitipanData['donatur_id'] = donaturId; + } + + await client.from('penitipan_bantuan').insert(penitipanData); + } catch (e) { + print('Error adding penitipan bantuan: $e'); + throw e.toString(); + } + } + Future?> getDonaturById(String donaturId) async { try { final response = @@ -563,6 +614,56 @@ class SupabaseService extends GetxService { } } + // Metode untuk mencari donatur berdasarkan keyword + Future>?> searchDonatur(String keyword) async { + try { + if (keyword.length < 3) { + return []; + } + + final response = await client + .from('donatur') + .select('*') + .ilike('nama', '%$keyword%') + .limit(10); + + return response; + } catch (e) { + print('Error searching donatur: $e'); + return null; + } + } + + // Metode untuk mendapatkan daftar donatur + Future>?> getDaftarDonatur() async { + try { + final response = await client + .from('donatur') + .select('*') + .order('nama', ascending: true); + + return response; + } catch (e) { + print('Error getting daftar donatur: $e'); + return null; + } + } + + // Metode untuk menambahkan donatur baru + Future tambahDonatur(Map donaturData) async { + try { + final response = + await client.from('donatur').insert(donaturData).select('id'); + if (response.isNotEmpty) { + return response[0]['id']; + } + return null; + } catch (e) { + print('Error adding donatur: $e'); + throw e.toString(); + } + } + // Pengaduan methods Future>?> getPengaduan() async { try {