Perbarui model dan tampilan untuk mendukung penyaluran baru
- Hapus properti tanggalPenjadwalan dari model PenyaluranBantuanModel - Ganti referensi tanggalPenjadwalan dengan createdAt di tampilan PermintaanPenjadwalanWidget dan PermintaanPenjadwalanView - Tambahkan fungsi baru untuk menambahkan penyaluran di JadwalPenyaluranController - Tambahkan rute dan tampilan untuk menambah penyaluran di aplikasi - Perbarui SupabaseService untuk menyimpan data penyaluran baru ke database
This commit is contained in:
60
lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart
Normal file
60
lib/app/data/models/pengajuan_kelayakan_bantuan_model.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
enum StatusKelayakan { pending, disetujui, ditolak }
|
||||||
|
|
||||||
|
class PengajuanKelayakanBantuanModel {
|
||||||
|
final String? id;
|
||||||
|
final String? buktiKelayakan;
|
||||||
|
final String? alasanVerifikasi;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? skemaBantuanId;
|
||||||
|
final String? wargaId;
|
||||||
|
final StatusKelayakan? status;
|
||||||
|
|
||||||
|
PengajuanKelayakanBantuanModel({
|
||||||
|
this.id,
|
||||||
|
this.buktiKelayakan,
|
||||||
|
this.alasanVerifikasi,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.skemaBantuanId,
|
||||||
|
this.wargaId,
|
||||||
|
this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PengajuanKelayakanBantuanModel.fromRawJson(String str) =>
|
||||||
|
PengajuanKelayakanBantuanModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory PengajuanKelayakanBantuanModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
PengajuanKelayakanBantuanModel(
|
||||||
|
id: json["id"],
|
||||||
|
buktiKelayakan: json["bukti_kelayakan"],
|
||||||
|
alasanVerifikasi: json["alasan_verifikasi"],
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
|
skemaBantuanId: json["skema_bantuan_id"],
|
||||||
|
wargaId: json["warga_id"],
|
||||||
|
status: json["status"] != null
|
||||||
|
? StatusKelayakan.values.firstWhere(
|
||||||
|
(e) => e.toString() == 'StatusKelayakan.${json["status"]}')
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id,
|
||||||
|
"bukti_kelayakan": buktiKelayakan,
|
||||||
|
"alasan_verifikasi": alasanVerifikasi,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
|
"skema_bantuan_id": skemaBantuanId,
|
||||||
|
"warga_id": wargaId,
|
||||||
|
"status": status?.toString().split('.').last,
|
||||||
|
};
|
||||||
|
}
|
@ -8,7 +8,6 @@ class PenyaluranBantuanModel {
|
|||||||
final String? petugasId;
|
final String? petugasId;
|
||||||
final String? status;
|
final String? status;
|
||||||
final String? alasanPenolakan;
|
final String? alasanPenolakan;
|
||||||
final DateTime? tanggalPenjadwalan;
|
|
||||||
final DateTime? tanggalPenyaluran;
|
final DateTime? tanggalPenyaluran;
|
||||||
final String? kategoriBantuanId;
|
final String? kategoriBantuanId;
|
||||||
final DateTime? tanggalPermintaan;
|
final DateTime? tanggalPermintaan;
|
||||||
@ -25,7 +24,6 @@ class PenyaluranBantuanModel {
|
|||||||
this.petugasId,
|
this.petugasId,
|
||||||
this.status,
|
this.status,
|
||||||
this.alasanPenolakan,
|
this.alasanPenolakan,
|
||||||
this.tanggalPenjadwalan,
|
|
||||||
this.tanggalPenyaluran,
|
this.tanggalPenyaluran,
|
||||||
this.kategoriBantuanId,
|
this.kategoriBantuanId,
|
||||||
this.tanggalPermintaan,
|
this.tanggalPermintaan,
|
||||||
@ -49,9 +47,6 @@ class PenyaluranBantuanModel {
|
|||||||
petugasId: json["petugas_id"],
|
petugasId: json["petugas_id"],
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
alasanPenolakan: json["alasan_penolakan"],
|
alasanPenolakan: json["alasan_penolakan"],
|
||||||
tanggalPenjadwalan: json["tanggal_penjadwalan"] != null
|
|
||||||
? DateTime.parse(json["tanggal_penjadwalan"]).toUtc()
|
|
||||||
: null,
|
|
||||||
tanggalPenyaluran: json["tanggal_penyaluran"] != null
|
tanggalPenyaluran: json["tanggal_penyaluran"] != null
|
||||||
? DateTime.parse(json["tanggal_penyaluran"]).toUtc()
|
? DateTime.parse(json["tanggal_penyaluran"]).toUtc()
|
||||||
: null,
|
: null,
|
||||||
@ -77,7 +72,6 @@ class PenyaluranBantuanModel {
|
|||||||
"petugas_id": petugasId,
|
"petugas_id": petugasId,
|
||||||
"status": status,
|
"status": status,
|
||||||
"alasan_penolakan": alasanPenolakan,
|
"alasan_penolakan": alasanPenolakan,
|
||||||
"tanggal_penjadwalan": tanggalPenjadwalan?.toUtc().toIso8601String(),
|
|
||||||
"tanggal_penyaluran": tanggalPenyaluran?.toUtc().toIso8601String(),
|
"tanggal_penyaluran": tanggalPenyaluran?.toUtc().toIso8601String(),
|
||||||
"kategori_bantuan_id": kategoriBantuanId,
|
"kategori_bantuan_id": kategoriBantuanId,
|
||||||
"tanggal_permintaan": tanggalPermintaan?.toUtc().toIso8601String(),
|
"tanggal_permintaan": tanggalPermintaan?.toUtc().toIso8601String(),
|
||||||
|
63
lib/app/data/models/skema_bantuan_model.dart
Normal file
63
lib/app/data/models/skema_bantuan_model.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class SkemaBantuanModel {
|
||||||
|
final String? id;
|
||||||
|
final int? petugasVerifikasiId;
|
||||||
|
final String? nama;
|
||||||
|
final String? deskripsi;
|
||||||
|
final int? kuota;
|
||||||
|
final String? kriteria;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? stokBantuanId;
|
||||||
|
final String? kategoriBantuanId;
|
||||||
|
|
||||||
|
SkemaBantuanModel({
|
||||||
|
this.id,
|
||||||
|
this.petugasVerifikasiId,
|
||||||
|
this.nama,
|
||||||
|
this.deskripsi,
|
||||||
|
this.kuota,
|
||||||
|
this.kriteria,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.stokBantuanId,
|
||||||
|
this.kategoriBantuanId,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SkemaBantuanModel.fromRawJson(String str) =>
|
||||||
|
SkemaBantuanModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory SkemaBantuanModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
SkemaBantuanModel(
|
||||||
|
id: json["id"],
|
||||||
|
petugasVerifikasiId: json["petugas_verifikasi_id"],
|
||||||
|
nama: json["nama"],
|
||||||
|
deskripsi: json["deskripsi"],
|
||||||
|
kuota: json["kuota"],
|
||||||
|
kriteria: json["kriteria"],
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
|
stokBantuanId: json["stok_bantuan_id"],
|
||||||
|
kategoriBantuanId: json["kategori_bantuan_id"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id,
|
||||||
|
"petugas_verifikasi_id": petugasVerifikasiId,
|
||||||
|
"nama": nama,
|
||||||
|
"deskripsi": deskripsi,
|
||||||
|
"kuota": kuota,
|
||||||
|
"kriteria": kriteria,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
|
"stok_bantuan_id": stokBantuanId,
|
||||||
|
"kategori_bantuan_id": kategoriBantuanId,
|
||||||
|
};
|
||||||
|
}
|
@ -200,7 +200,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: jadwal.id,
|
value: jadwal.id,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
|
'${jadwal.createdAt?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
@ -260,16 +260,17 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> rejectJadwal(String jadwalId, String alasan) async {
|
Future<void> rejectJadwal(String jadwalId, String alasanPenolakan) async {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
await _supabaseService.rejectJadwal(jadwalId, alasan);
|
await _supabaseService.rejectJadwal(jadwalId, alasanPenolakan);
|
||||||
await loadPermintaanPenjadwalanData();
|
await loadPermintaanPenjadwalanData();
|
||||||
|
await loadJadwalData();
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Sukses',
|
'Sukses',
|
||||||
'Jadwal berhasil ditolak',
|
'Jadwal berhasil ditolak',
|
||||||
snackPosition: SnackPosition.TOP,
|
snackPosition: SnackPosition.TOP,
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.red,
|
||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -317,8 +318,8 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
await loadJadwalData();
|
await loadJadwalData();
|
||||||
await loadPermintaanPenjadwalanData();
|
await loadPermintaanPenjadwalanData();
|
||||||
await loadLokasiPenyaluranData();
|
} catch (e) {
|
||||||
await loadKategoriBantuanData();
|
print('Error refreshing data: $e');
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -327,4 +328,63 @@ class JadwalPenyaluranController extends GetxController {
|
|||||||
void changeCategory(int index) {
|
void changeCategory(int index) {
|
||||||
selectedCategoryIndex.value = index;
|
selectedCategoryIndex.value = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menambahkan penyaluran baru
|
||||||
|
Future<void> tambahPenyaluran({
|
||||||
|
required String nama,
|
||||||
|
required String deskripsi,
|
||||||
|
required String kategoriBantuanId,
|
||||||
|
required String lokasiPenyaluranId,
|
||||||
|
required int jumlahPenerima,
|
||||||
|
required DateTime? tanggalPenyaluran,
|
||||||
|
}) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Pastikan user sudah login dan memiliki ID
|
||||||
|
if (user?.id == null) {
|
||||||
|
throw Exception('User tidak terautentikasi');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat objek penyaluran
|
||||||
|
final penyaluran = {
|
||||||
|
'nama': nama,
|
||||||
|
'deskripsi': deskripsi,
|
||||||
|
'kategori_bantuan_id': kategoriBantuanId,
|
||||||
|
'lokasi_penyaluran_id': lokasiPenyaluranId,
|
||||||
|
'petugas_id': user!.id,
|
||||||
|
'jumlah_penerima': jumlahPenerima,
|
||||||
|
'tanggal_penyaluran': tanggalPenyaluran?.toUtc().toIso8601String(),
|
||||||
|
'status': 'DIJADWALKAN', // Status awal adalah terjadwal
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simpan ke database
|
||||||
|
await _supabaseService.tambahPenyaluran(penyaluran);
|
||||||
|
|
||||||
|
// Refresh data
|
||||||
|
await loadJadwalData();
|
||||||
|
|
||||||
|
// Kembali ke halaman sebelumnya
|
||||||
|
Get.back();
|
||||||
|
|
||||||
|
// Tampilkan notifikasi sukses
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penyaluran berhasil ditambahkan',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error menambahkan penyaluran: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menambahkan penyaluran: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.TOP,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'package:penyaluran_app/app/theme/app_theme.dart';
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/jadwal_section_widget.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/permintaan_penjadwalan_summary_widget.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/components/calendar_view_widget.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/components/calendar_view_widget.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/tambah_penyaluran_view.dart';
|
||||||
|
|
||||||
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||||
const PenyaluranView({super.key});
|
const PenyaluranView({super.key});
|
||||||
@ -13,29 +14,36 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
child: Column(
|
child: Scaffold(
|
||||||
children: [
|
body: Column(
|
||||||
TabBar(
|
children: [
|
||||||
tabs: const [
|
TabBar(
|
||||||
Tab(text: 'Daftar Jadwal'),
|
tabs: const [
|
||||||
Tab(text: 'Kalender'),
|
Tab(text: 'Daftar Jadwal'),
|
||||||
],
|
Tab(text: 'Kalender'),
|
||||||
labelColor: AppTheme.primaryColor,
|
|
||||||
indicatorColor: AppTheme.primaryColor,
|
|
||||||
unselectedLabelColor: Colors.grey,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
children: [
|
|
||||||
// Tab 1: Daftar Jadwal
|
|
||||||
_buildJadwalListView(),
|
|
||||||
|
|
||||||
// Tab 2: Kalender
|
|
||||||
_buildCalendarView(),
|
|
||||||
],
|
],
|
||||||
|
labelColor: AppTheme.primaryColor,
|
||||||
|
indicatorColor: AppTheme.primaryColor,
|
||||||
|
unselectedLabelColor: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: TabBarView(
|
||||||
|
children: [
|
||||||
|
// Tab 1: Daftar Jadwal
|
||||||
|
_buildJadwalListView(),
|
||||||
|
|
||||||
|
// Tab 2: Kalender
|
||||||
|
_buildCalendarView(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () => Get.to(() => const TambahPenyaluranView()),
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
child: const Icon(Icons.add, color: Colors.white),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,7 @@ class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
|||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: jadwal.id,
|
value: jadwal.id,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
|
'${jadwal.createdAt?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.nama ?? ''})'),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
355
lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart
Normal file
355
lib/app/modules/petugas_desa/views/tambah_penyaluran_view.dart
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class TambahPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||||
|
const TambahPenyaluranView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Tambah Penyaluran Bantuan'),
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
body: _buildTambahPenyaluranForm(context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTambahPenyaluranForm(BuildContext context) {
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final TextEditingController namaController = TextEditingController();
|
||||||
|
final TextEditingController deskripsiController = TextEditingController();
|
||||||
|
final TextEditingController jumlahPenerimaController =
|
||||||
|
TextEditingController();
|
||||||
|
final TextEditingController tanggalPenyaluranController =
|
||||||
|
TextEditingController();
|
||||||
|
final TextEditingController waktuPenyaluranController =
|
||||||
|
TextEditingController();
|
||||||
|
|
||||||
|
// Variabel untuk menyimpan nilai yang dipilih
|
||||||
|
final Rx<String?> selectedKategoriBantuanId = Rx<String?>(null);
|
||||||
|
final Rx<String?> selectedLokasiPenyaluranId = Rx<String?>(null);
|
||||||
|
|
||||||
|
// Tanggal dan waktu penyaluran
|
||||||
|
final Rx<DateTime?> selectedDate = Rx<DateTime?>(null);
|
||||||
|
final Rx<TimeOfDay?> selectedTime = Rx<TimeOfDay?>(null);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Judul Form
|
||||||
|
Text(
|
||||||
|
'Formulir Penyaluran Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Nama Penyaluran
|
||||||
|
Text(
|
||||||
|
'Nama Penyaluran',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: namaController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Masukkan nama penyaluran',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama penyaluran tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Kategori Bantuan
|
||||||
|
Text(
|
||||||
|
'Kategori Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Obx(() => DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hint: const Text('Pilih kategori bantuan'),
|
||||||
|
value: selectedKategoriBantuanId.value,
|
||||||
|
items: controller.kategoriBantuanCache.entries
|
||||||
|
.map((entry) => DropdownMenuItem<String>(
|
||||||
|
value: entry.key,
|
||||||
|
child: Text(entry.value.nama ?? 'Tidak ada nama'),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedKategoriBantuanId.value = value;
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Kategori bantuan harus dipilih';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Lokasi Penyaluran
|
||||||
|
Text(
|
||||||
|
'Lokasi Penyaluran',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Obx(() => DropdownButtonFormField<String>(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
hint: const Text('Pilih lokasi penyaluran'),
|
||||||
|
value: selectedLokasiPenyaluranId.value,
|
||||||
|
items: controller.lokasiPenyaluranCache.entries
|
||||||
|
.map((entry) => DropdownMenuItem<String>(
|
||||||
|
value: entry.key,
|
||||||
|
child: Text(entry.value.nama),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedLokasiPenyaluranId.value = value;
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Lokasi penyaluran harus dipilih';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Jumlah Penerima
|
||||||
|
Text(
|
||||||
|
'Jumlah Penerima',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: jumlahPenerimaController,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Masukkan jumlah penerima',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Jumlah penerima tidak boleh kosong';
|
||||||
|
}
|
||||||
|
if (int.tryParse(value) == null) {
|
||||||
|
return 'Jumlah penerima harus berupa angka';
|
||||||
|
}
|
||||||
|
if (int.parse(value) <= 0) {
|
||||||
|
return 'Jumlah penerima harus lebih dari 0';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Tanggal Penyaluran
|
||||||
|
Text(
|
||||||
|
'Tanggal Penyaluran',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: tanggalPenyaluranController,
|
||||||
|
readOnly: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Pilih tanggal penyaluran',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
suffixIcon: const Icon(Icons.calendar_today),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
final DateTime? pickedDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: DateTime.now(),
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||||
|
);
|
||||||
|
if (pickedDate != null) {
|
||||||
|
selectedDate.value = pickedDate;
|
||||||
|
tanggalPenyaluranController.text =
|
||||||
|
DateFormat('dd MMMM yyyy', 'id_ID').format(pickedDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Tanggal penyaluran harus dipilih';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Waktu Penyaluran
|
||||||
|
Text(
|
||||||
|
'Waktu Penyaluran',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: waktuPenyaluranController,
|
||||||
|
readOnly: true,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Pilih waktu penyaluran',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
suffixIcon: const Icon(Icons.access_time),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
final TimeOfDay? pickedTime = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: TimeOfDay.now(),
|
||||||
|
);
|
||||||
|
if (pickedTime != null) {
|
||||||
|
selectedTime.value = pickedTime;
|
||||||
|
waktuPenyaluranController.text =
|
||||||
|
'${pickedTime.hour.toString().padLeft(2, '0')}:${pickedTime.minute.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Waktu penyaluran harus dipilih';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Deskripsi
|
||||||
|
Text(
|
||||||
|
'Deskripsi',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: deskripsiController,
|
||||||
|
maxLines: 4,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Masukkan deskripsi penyaluran',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Deskripsi tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Tombol Submit
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
// Gabungkan tanggal dan waktu
|
||||||
|
DateTime? tanggalWaktuPenyaluran;
|
||||||
|
if (selectedDate.value != null &&
|
||||||
|
selectedTime.value != null) {
|
||||||
|
tanggalWaktuPenyaluran = DateTime(
|
||||||
|
selectedDate.value!.year,
|
||||||
|
selectedDate.value!.month,
|
||||||
|
selectedDate.value!.day,
|
||||||
|
selectedTime.value!.hour,
|
||||||
|
selectedTime.value!.minute,
|
||||||
|
).toLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panggil fungsi untuk menambahkan penyaluran
|
||||||
|
controller.tambahPenyaluran(
|
||||||
|
nama: namaController.text,
|
||||||
|
deskripsi: deskripsiController.text,
|
||||||
|
kategoriBantuanId: selectedKategoriBantuanId.value!,
|
||||||
|
lokasiPenyaluranId: selectedLokasiPenyaluranId.value!,
|
||||||
|
jumlahPenerima:
|
||||||
|
int.parse(jumlahPenerimaController.text),
|
||||||
|
tanggalPenyaluran: tanggalWaktuPenyaluran,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Simpan Penyaluran',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import 'package:penyaluran_app/app/modules/petugas_desa/views/pelaksanaan_penyal
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_penitipan_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/riwayat_penitipan_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_donatur_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/daftar_donatur_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_donatur_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/detail_donatur_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/tambah_penyaluran_view.dart';
|
||||||
|
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/penerima_binding.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/bindings/donatur_binding.dart';
|
||||||
@ -87,5 +88,10 @@ class AppPages {
|
|||||||
page: () => const DetailDonaturView(),
|
page: () => const DetailDonaturView(),
|
||||||
binding: DonaturBinding(),
|
binding: DonaturBinding(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: _Paths.tambahPenyaluran,
|
||||||
|
page: () => const TambahPenyaluranView(),
|
||||||
|
binding: PetugasDesaBinding(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ abstract class Routes {
|
|||||||
static const riwayatPenitipan = _Paths.riwayatPenitipan;
|
static const riwayatPenitipan = _Paths.riwayatPenitipan;
|
||||||
static const daftarDonatur = _Paths.daftarDonatur;
|
static const daftarDonatur = _Paths.daftarDonatur;
|
||||||
static const detailDonatur = _Paths.detailDonatur;
|
static const detailDonatur = _Paths.detailDonatur;
|
||||||
|
static const tambahPenyaluran = _Paths.tambahPenyaluran;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Paths {
|
abstract class _Paths {
|
||||||
@ -40,4 +41,5 @@ abstract class _Paths {
|
|||||||
static const riwayatPenitipan = '/petugas-desa/riwayat-penitipan';
|
static const riwayatPenitipan = '/petugas-desa/riwayat-penitipan';
|
||||||
static const daftarDonatur = '/daftar-donatur';
|
static const daftarDonatur = '/daftar-donatur';
|
||||||
static const detailDonatur = '/daftar-donatur/detail';
|
static const detailDonatur = '/daftar-donatur/detail';
|
||||||
|
static const tambahPenyaluran = '/tambah-penyaluran';
|
||||||
}
|
}
|
||||||
|
@ -1122,4 +1122,14 @@ class SupabaseService extends GetxService {
|
|||||||
// Tipe data lainnya
|
// Tipe data lainnya
|
||||||
print('$prefix Data: $data (${data.runtimeType})');
|
print('$prefix Data: $data (${data.runtimeType})');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menambahkan penyaluran baru
|
||||||
|
Future<void> tambahPenyaluran(Map<String, dynamic> penyaluran) async {
|
||||||
|
try {
|
||||||
|
await client.from('penyaluran_bantuan').insert(penyaluran);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error menambahkan penyaluran: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user