ambil data stok bantuan
This commit is contained in:
@ -1,19 +1,21 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class BentukBantuanModel {
|
class BentukBantuanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String nama;
|
final String? nama;
|
||||||
final String? deskripsi;
|
final String? deskripsi;
|
||||||
final String? kategori;
|
final String? kategori;
|
||||||
final DateTime createdAt;
|
final String? satuan;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
BentukBantuanModel({
|
BentukBantuanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.nama,
|
this.nama,
|
||||||
this.deskripsi,
|
this.deskripsi,
|
||||||
this.kategori,
|
this.kategori,
|
||||||
required this.createdAt,
|
this.satuan,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -28,10 +30,13 @@ class BentukBantuanModel {
|
|||||||
nama: json["nama"],
|
nama: json["nama"],
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
kategori: json["kategori"],
|
kategori: json["kategori"],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
satuan: json["satuan"],
|
||||||
updatedAt: json["updated_at"] == null
|
createdAt: json["created_at"] != null
|
||||||
? null
|
? DateTime.parse(json["created_at"])
|
||||||
: DateTime.parse(json["updated_at"]),
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@ -39,7 +44,8 @@ class BentukBantuanModel {
|
|||||||
"nama": nama,
|
"nama": nama,
|
||||||
"deskripsi": deskripsi,
|
"deskripsi": deskripsi,
|
||||||
"kategori": kategori,
|
"kategori": kategori,
|
||||||
"created_at": createdAt.toIso8601String(),
|
"satuan": satuan,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
50
lib/app/data/models/desa_model.dart
Normal file
50
lib/app/data/models/desa_model.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class DesaModel {
|
||||||
|
final String id;
|
||||||
|
final String nama;
|
||||||
|
final String? kecamatan;
|
||||||
|
final String? kabupaten;
|
||||||
|
final String? provinsi;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
|
DesaModel({
|
||||||
|
required this.id,
|
||||||
|
required this.nama,
|
||||||
|
this.kecamatan,
|
||||||
|
this.kabupaten,
|
||||||
|
this.provinsi,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory DesaModel.fromRawJson(String str) =>
|
||||||
|
DesaModel.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
|
factory DesaModel.fromJson(Map<String, dynamic> json) => DesaModel(
|
||||||
|
id: json["id"],
|
||||||
|
nama: json["nama"],
|
||||||
|
kecamatan: json["kecamatan"],
|
||||||
|
kabupaten: json["kabupaten"],
|
||||||
|
provinsi: json["provinsi"],
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"id": id,
|
||||||
|
"nama": nama,
|
||||||
|
"kecamatan": kecamatan,
|
||||||
|
"kabupaten": kabupaten,
|
||||||
|
"provinsi": provinsi,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
}
|
@ -1,23 +1,27 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class DonaturModel {
|
class DonaturModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String nama;
|
final String? nama;
|
||||||
final String? alamat;
|
final String? alamat;
|
||||||
final String? noTelp;
|
final String? telepon;
|
||||||
final String? email;
|
final String? email;
|
||||||
final String? jenisDonatur; // Individu, Organisasi, Perusahaan, dll
|
final String? jenis;
|
||||||
final DateTime createdAt;
|
final String? deskripsi;
|
||||||
|
final String? status;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
DonaturModel({
|
DonaturModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.nama,
|
this.nama,
|
||||||
this.alamat,
|
this.alamat,
|
||||||
this.noTelp,
|
this.telepon,
|
||||||
this.email,
|
this.email,
|
||||||
this.jenisDonatur,
|
this.jenis,
|
||||||
required this.createdAt,
|
this.deskripsi,
|
||||||
|
this.status,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -30,23 +34,29 @@ class DonaturModel {
|
|||||||
id: json["id"],
|
id: json["id"],
|
||||||
nama: json["nama"],
|
nama: json["nama"],
|
||||||
alamat: json["alamat"],
|
alamat: json["alamat"],
|
||||||
noTelp: json["no_telp"],
|
telepon: json["telepon"],
|
||||||
email: json["email"],
|
email: json["email"],
|
||||||
jenisDonatur: json["jenis_donatur"],
|
jenis: json["jenis"],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
deskripsi: json["deskripsi"],
|
||||||
updatedAt: json["updated_at"] == null
|
status: json["status"],
|
||||||
? null
|
createdAt: json["created_at"] != null
|
||||||
: DateTime.parse(json["updated_at"]),
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
"nama": nama,
|
"nama": nama,
|
||||||
"alamat": alamat,
|
"alamat": alamat,
|
||||||
"no_telp": noTelp,
|
"telepon": telepon,
|
||||||
"email": email,
|
"email": email,
|
||||||
"jenis_donatur": jenisDonatur,
|
"jenis": jenis,
|
||||||
"created_at": createdAt.toIso8601String(),
|
"deskripsi": deskripsi,
|
||||||
|
"status": status,
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class LaporanModel {
|
class LaporanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String judul;
|
final String? judul;
|
||||||
final String? deskripsi;
|
final String? deskripsi;
|
||||||
final String jenis; // Contoh: 'penyaluran', 'penitipan', 'pengaduan'
|
final String? jenis; // Contoh: 'PENYALURAN', 'STOK_BANTUAN', 'PENERIMA'
|
||||||
final String?
|
final String?
|
||||||
referensiId; // ID dari entitas yang dilaporkan (penyaluran, penitipan, dll)
|
referensiId; // ID dari entitas yang dilaporkan (penyaluran, penitipan, dll)
|
||||||
final String status; // Contoh: 'draft', 'final', 'disetujui'
|
final String? status; // Contoh: 'draft', 'final', 'disetujui'
|
||||||
final String? userId; // Pengguna yang membuat laporan
|
final String? petugasId; // Pengguna yang membuat laporan
|
||||||
final List<String>? fileUrls; // URL file laporan (PDF, Excel, dll)
|
final List<String>? fileUrls; // URL file laporan (PDF, Excel, dll)
|
||||||
final DateTime periodeAwal;
|
final DateTime? tanggalMulai;
|
||||||
final DateTime periodeAkhir;
|
final DateTime? tanggalSelesai;
|
||||||
final DateTime tanggalLaporan;
|
final DateTime? tanggalLaporan;
|
||||||
final DateTime createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
LaporanModel({
|
LaporanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.judul,
|
this.judul,
|
||||||
this.deskripsi,
|
this.deskripsi,
|
||||||
required this.jenis,
|
this.jenis,
|
||||||
this.referensiId,
|
this.referensiId,
|
||||||
required this.status,
|
this.status,
|
||||||
this.userId,
|
this.petugasId,
|
||||||
this.fileUrls,
|
this.fileUrls,
|
||||||
required this.periodeAwal,
|
this.tanggalMulai,
|
||||||
required this.periodeAkhir,
|
this.tanggalSelesai,
|
||||||
required this.tanggalLaporan,
|
this.tanggalLaporan,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -44,17 +44,29 @@ class LaporanModel {
|
|||||||
jenis: json["jenis"],
|
jenis: json["jenis"],
|
||||||
referensiId: json["referensi_id"],
|
referensiId: json["referensi_id"],
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
userId: json["user_id"],
|
petugasId: json["petugas_id"] ?? json["user_id"],
|
||||||
fileUrls: json["file_urls"] == null
|
fileUrls: json["file_urls"] == null
|
||||||
? null
|
? null
|
||||||
: List<String>.from(json["file_urls"].map((x) => x)),
|
: List<String>.from(json["file_urls"].map((x) => x)),
|
||||||
periodeAwal: DateTime.parse(json["periode_awal"]),
|
tanggalMulai: json["tanggal_mulai"] != null
|
||||||
periodeAkhir: DateTime.parse(json["periode_akhir"]),
|
? DateTime.parse(json["tanggal_mulai"])
|
||||||
tanggalLaporan: DateTime.parse(json["tanggal_laporan"]),
|
: json["periode_awal"] != null
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
? DateTime.parse(json["periode_awal"])
|
||||||
updatedAt: json["updated_at"] == null
|
: null,
|
||||||
? null
|
tanggalSelesai: json["tanggal_selesai"] != null
|
||||||
: DateTime.parse(json["updated_at"]),
|
? DateTime.parse(json["tanggal_selesai"])
|
||||||
|
: json["periode_akhir"] != null
|
||||||
|
? DateTime.parse(json["periode_akhir"])
|
||||||
|
: null,
|
||||||
|
tanggalLaporan: json["tanggal_laporan"] != null
|
||||||
|
? DateTime.parse(json["tanggal_laporan"])
|
||||||
|
: null,
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@ -64,14 +76,14 @@ class LaporanModel {
|
|||||||
"jenis": jenis,
|
"jenis": jenis,
|
||||||
"referensi_id": referensiId,
|
"referensi_id": referensiId,
|
||||||
"status": status,
|
"status": status,
|
||||||
"user_id": userId,
|
"petugas_id": petugasId,
|
||||||
"file_urls": fileUrls == null
|
"file_urls": fileUrls == null
|
||||||
? null
|
? null
|
||||||
: List<dynamic>.from(fileUrls!.map((x) => x)),
|
: List<dynamic>.from(fileUrls!.map((x) => x)),
|
||||||
"periode_awal": periodeAwal.toIso8601String(),
|
"tanggal_mulai": tanggalMulai?.toIso8601String(),
|
||||||
"periode_akhir": periodeAkhir.toIso8601String(),
|
"tanggal_selesai": tanggalSelesai?.toIso8601String(),
|
||||||
"tanggal_laporan": tanggalLaporan.toIso8601String(),
|
"tanggal_laporan": tanggalLaporan?.toIso8601String(),
|
||||||
"created_at": createdAt.toIso8601String(),
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,8 @@ class LokasiPenyaluranModel {
|
|||||||
kabupaten: json["kabupaten"],
|
kabupaten: json["kabupaten"],
|
||||||
provinsi: json["provinsi"],
|
provinsi: json["provinsi"],
|
||||||
kodePos: json["kode_pos"],
|
kodePos: json["kode_pos"],
|
||||||
latitude: json["latitude"] != null ? json["latitude"].toDouble() : null,
|
latitude: json["latitude"]?.toDouble(),
|
||||||
longitude:
|
longitude: json["longitude"]?.toDouble(),
|
||||||
json["longitude"] != null ? json["longitude"].toDouble() : null,
|
|
||||||
petugasDesaId: json["petugas_desa_id"],
|
petugasDesaId: json["petugas_desa_id"],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
createdAt: DateTime.parse(json["created_at"]),
|
||||||
updatedAt: json["updated_at"] == null
|
updatedAt: json["updated_at"] == null
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class NotifikasiModel {
|
class NotifikasiModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String judul;
|
final String? userId;
|
||||||
final String pesan;
|
final String? judul;
|
||||||
final String? jenis; // Contoh: 'penyaluran', 'penitipan', 'pengaduan'
|
final String? pesan;
|
||||||
final String? referensiId; // ID dari entitas yang terkait notifikasi
|
final String? jenis;
|
||||||
final String? userId; // Pengguna yang menerima notifikasi
|
final String? referensiId;
|
||||||
final bool dibaca;
|
final bool? dibaca;
|
||||||
final DateTime createdAt;
|
final DateTime? tanggalNotifikasi;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
NotifikasiModel({
|
NotifikasiModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.judul,
|
this.userId,
|
||||||
required this.pesan,
|
this.judul,
|
||||||
|
this.pesan,
|
||||||
this.jenis,
|
this.jenis,
|
||||||
this.referensiId,
|
this.referensiId,
|
||||||
this.userId,
|
this.dibaca,
|
||||||
this.dibaca = false,
|
this.tanggalNotifikasi,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -31,27 +33,33 @@ class NotifikasiModel {
|
|||||||
factory NotifikasiModel.fromJson(Map<String, dynamic> json) =>
|
factory NotifikasiModel.fromJson(Map<String, dynamic> json) =>
|
||||||
NotifikasiModel(
|
NotifikasiModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
|
userId: json["user_id"],
|
||||||
judul: json["judul"],
|
judul: json["judul"],
|
||||||
pesan: json["pesan"],
|
pesan: json["pesan"],
|
||||||
jenis: json["jenis"],
|
jenis: json["jenis"],
|
||||||
referensiId: json["referensi_id"],
|
referensiId: json["referensi_id"],
|
||||||
userId: json["user_id"],
|
dibaca: json["dibaca"],
|
||||||
dibaca: json["dibaca"] ?? false,
|
tanggalNotifikasi: json["tanggal_notifikasi"] != null
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
? DateTime.parse(json["tanggal_notifikasi"])
|
||||||
updatedAt: json["updated_at"] == null
|
: null,
|
||||||
? null
|
createdAt: json["created_at"] != null
|
||||||
: DateTime.parse(json["updated_at"]),
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
|
"user_id": userId,
|
||||||
"judul": judul,
|
"judul": judul,
|
||||||
"pesan": pesan,
|
"pesan": pesan,
|
||||||
"jenis": jenis,
|
"jenis": jenis,
|
||||||
"referensi_id": referensiId,
|
"referensi_id": referensiId,
|
||||||
"user_id": userId,
|
|
||||||
"dibaca": dibaca,
|
"dibaca": dibaca,
|
||||||
"created_at": createdAt.toIso8601String(),
|
"tanggal_notifikasi": tanggalNotifikasi?.toIso8601String(),
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class PengaduanModel {
|
class PengaduanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String judul;
|
final String? judul;
|
||||||
final String deskripsi;
|
final String? deskripsi;
|
||||||
final String? userId; // Pengguna yang membuat pengaduan
|
final String? status;
|
||||||
final String? penyaluranBantuanId; // Referensi ke PenyaluranBantuan
|
final String? kategori;
|
||||||
final String status; // Contoh: 'pending', 'diproses', 'selesai'
|
final String? pelapor;
|
||||||
final List<String>? gambarUrls; // URL gambar bukti pengaduan
|
final String? kontakPelapor;
|
||||||
final DateTime createdAt;
|
final List<String>? gambarUrls;
|
||||||
|
final DateTime? tanggalPengaduan;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
PengaduanModel({
|
PengaduanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.judul,
|
this.judul,
|
||||||
required this.deskripsi,
|
this.deskripsi,
|
||||||
this.userId,
|
this.status,
|
||||||
this.penyaluranBantuanId,
|
this.kategori,
|
||||||
required this.status,
|
this.pelapor,
|
||||||
|
this.kontakPelapor,
|
||||||
this.gambarUrls,
|
this.gambarUrls,
|
||||||
required this.createdAt,
|
this.tanggalPengaduan,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,29 +36,37 @@ class PengaduanModel {
|
|||||||
id: json["id"],
|
id: json["id"],
|
||||||
judul: json["judul"],
|
judul: json["judul"],
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
userId: json["user_id"],
|
|
||||||
penyaluranBantuanId: json["penyaluran_bantuan_id"],
|
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
|
kategori: json["kategori"],
|
||||||
|
pelapor: json["pelapor"],
|
||||||
|
kontakPelapor: json["kontak_pelapor"],
|
||||||
gambarUrls: json["gambar_urls"] == null
|
gambarUrls: json["gambar_urls"] == null
|
||||||
? null
|
? null
|
||||||
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
tanggalPengaduan: json["tanggal_pengaduan"] != null
|
||||||
updatedAt: json["updated_at"] == null
|
? DateTime.parse(json["tanggal_pengaduan"])
|
||||||
? null
|
: null,
|
||||||
: DateTime.parse(json["updated_at"]),
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
"judul": judul,
|
"judul": judul,
|
||||||
"deskripsi": deskripsi,
|
"deskripsi": deskripsi,
|
||||||
"user_id": userId,
|
|
||||||
"penyaluran_bantuan_id": penyaluranBantuanId,
|
|
||||||
"status": status,
|
"status": status,
|
||||||
|
"kategori": kategori,
|
||||||
|
"pelapor": pelapor,
|
||||||
|
"kontak_pelapor": kontakPelapor,
|
||||||
"gambar_urls": gambarUrls == null
|
"gambar_urls": gambarUrls == null
|
||||||
? null
|
? null
|
||||||
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
||||||
"created_at": createdAt.toIso8601String(),
|
"tanggal_pengaduan": tanggalPengaduan?.toIso8601String(),
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,35 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class PenitipanBantuanModel {
|
class PenitipanBantuanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String? donaturId; // Referensi ke Donatur
|
final String? donaturId;
|
||||||
final String? bentukBantuanId; // Referensi ke BentukBantuan
|
final String? bentukBantuanId;
|
||||||
final String? sumberBantuanId; // Referensi ke SumberBantuan
|
final String? nama;
|
||||||
final double jumlah;
|
final double? jumlah;
|
||||||
final String? satuan; // Contoh: kg, buah, paket, dll
|
final String? satuan;
|
||||||
final String? deskripsi;
|
final String? deskripsi;
|
||||||
final String status; // Contoh: 'diterima', 'disalurkan', 'ditolak'
|
final String? status;
|
||||||
final List<String>? gambarUrls; // URL gambar bukti penitipan
|
final String? alasanPenolakan;
|
||||||
final DateTime tanggalPenitipan;
|
final List<String>? gambarUrls;
|
||||||
final DateTime createdAt;
|
final DateTime? tanggalPenitipan;
|
||||||
|
final DateTime? tanggalVerifikasi;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
PenitipanBantuanModel({
|
PenitipanBantuanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
this.donaturId,
|
this.donaturId,
|
||||||
this.bentukBantuanId,
|
this.bentukBantuanId,
|
||||||
this.sumberBantuanId,
|
this.nama,
|
||||||
required this.jumlah,
|
this.jumlah,
|
||||||
this.satuan,
|
this.satuan,
|
||||||
this.deskripsi,
|
this.deskripsi,
|
||||||
required this.status,
|
this.status,
|
||||||
|
this.alasanPenolakan,
|
||||||
this.gambarUrls,
|
this.gambarUrls,
|
||||||
required this.tanggalPenitipan,
|
this.tanggalPenitipan,
|
||||||
required this.createdAt,
|
this.tanggalVerifikasi,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -39,35 +43,45 @@ class PenitipanBantuanModel {
|
|||||||
id: json["id"],
|
id: json["id"],
|
||||||
donaturId: json["donatur_id"],
|
donaturId: json["donatur_id"],
|
||||||
bentukBantuanId: json["bentuk_bantuan_id"],
|
bentukBantuanId: json["bentuk_bantuan_id"],
|
||||||
sumberBantuanId: json["sumber_bantuan_id"],
|
nama: json["nama"],
|
||||||
jumlah: json["jumlah"].toDouble(),
|
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
|
||||||
satuan: json["satuan"],
|
satuan: json["satuan"],
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
|
alasanPenolakan: json["alasan_penolakan"],
|
||||||
gambarUrls: json["gambar_urls"] == null
|
gambarUrls: json["gambar_urls"] == null
|
||||||
? null
|
? null
|
||||||
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
||||||
tanggalPenitipan: DateTime.parse(json["tanggal_penitipan"]),
|
tanggalPenitipan: json["tanggal_penitipan"] != null
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
? DateTime.parse(json["tanggal_penitipan"])
|
||||||
updatedAt: json["updated_at"] == null
|
: null,
|
||||||
? null
|
tanggalVerifikasi: json["tanggal_verifikasi"] != null
|
||||||
: DateTime.parse(json["updated_at"]),
|
? DateTime.parse(json["tanggal_verifikasi"])
|
||||||
|
: null,
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
"donatur_id": donaturId,
|
"donatur_id": donaturId,
|
||||||
"bentuk_bantuan_id": bentukBantuanId,
|
"bentuk_bantuan_id": bentukBantuanId,
|
||||||
"sumber_bantuan_id": sumberBantuanId,
|
"nama": nama,
|
||||||
"jumlah": jumlah,
|
"jumlah": jumlah,
|
||||||
"satuan": satuan,
|
"satuan": satuan,
|
||||||
"deskripsi": deskripsi,
|
"deskripsi": deskripsi,
|
||||||
"status": status,
|
"status": status,
|
||||||
|
"alasan_penolakan": alasanPenolakan,
|
||||||
"gambar_urls": gambarUrls == null
|
"gambar_urls": gambarUrls == null
|
||||||
? null
|
? null
|
||||||
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
||||||
"tanggal_penitipan": tanggalPenitipan.toIso8601String(),
|
"tanggal_penitipan": tanggalPenitipan?.toIso8601String(),
|
||||||
"created_at": createdAt.toIso8601String(),
|
"tanggal_verifikasi": tanggalVerifikasi?.toIso8601String(),
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,29 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class PenyaluranBantuanModel {
|
class PenyaluranBantuanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String? penitipanBantuanId; // Referensi ke PenitipanBantuan
|
final String? judul;
|
||||||
final String? lokasiPenyaluranId; // Referensi ke LokasiPenyaluran
|
|
||||||
final String? petugasDesaId; // Referensi ke PetugasDesa
|
|
||||||
final double jumlah;
|
|
||||||
final String? satuan; // Contoh: kg, buah, paket, dll
|
|
||||||
final String? deskripsi;
|
final String? deskripsi;
|
||||||
final String status; // Contoh: 'diproses', 'disalurkan', 'dibatalkan'
|
final String? lokasiPenyaluranId;
|
||||||
final List<String>? gambarUrls; // URL gambar bukti penyaluran
|
final String? petugasId;
|
||||||
final DateTime tanggalPenyaluran;
|
final String? status;
|
||||||
final DateTime createdAt;
|
final String? alasanPenolakan;
|
||||||
|
final DateTime? tanggalPenjadwalan;
|
||||||
|
final DateTime? tanggalPenyaluran;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
PenyaluranBantuanModel({
|
PenyaluranBantuanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
this.penitipanBantuanId,
|
this.judul,
|
||||||
this.lokasiPenyaluranId,
|
|
||||||
this.petugasDesaId,
|
|
||||||
required this.jumlah,
|
|
||||||
this.satuan,
|
|
||||||
this.deskripsi,
|
this.deskripsi,
|
||||||
required this.status,
|
this.lokasiPenyaluranId,
|
||||||
this.gambarUrls,
|
this.petugasId,
|
||||||
required this.tanggalPenyaluran,
|
this.status,
|
||||||
required this.createdAt,
|
this.alasanPenolakan,
|
||||||
|
this.tanggalPenjadwalan,
|
||||||
|
this.tanggalPenyaluran,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,37 +35,37 @@ class PenyaluranBantuanModel {
|
|||||||
factory PenyaluranBantuanModel.fromJson(Map<String, dynamic> json) =>
|
factory PenyaluranBantuanModel.fromJson(Map<String, dynamic> json) =>
|
||||||
PenyaluranBantuanModel(
|
PenyaluranBantuanModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
penitipanBantuanId: json["penitipan_bantuan_id"],
|
judul: json["judul"],
|
||||||
lokasiPenyaluranId: json["lokasi_penyaluran_id"],
|
|
||||||
petugasDesaId: json["petugas_desa_id"],
|
|
||||||
jumlah: json["jumlah"].toDouble(),
|
|
||||||
satuan: json["satuan"],
|
|
||||||
deskripsi: json["deskripsi"],
|
deskripsi: json["deskripsi"],
|
||||||
|
lokasiPenyaluranId: json["lokasi_penyaluran_id"],
|
||||||
|
petugasId: json["petugas_id"],
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
gambarUrls: json["gambar_urls"] == null
|
alasanPenolakan: json["alasan_penolakan"],
|
||||||
? null
|
tanggalPenjadwalan: json["tanggal_penjadwalan"] != null
|
||||||
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
? DateTime.parse(json["tanggal_penjadwalan"])
|
||||||
tanggalPenyaluran: DateTime.parse(json["tanggal_penyaluran"]),
|
: null,
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
tanggalPenyaluran: json["tanggal_penyaluran"] != null
|
||||||
updatedAt: json["updated_at"] == null
|
? DateTime.parse(json["tanggal_penyaluran"])
|
||||||
? null
|
: null,
|
||||||
: DateTime.parse(json["updated_at"]),
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
"penitipan_bantuan_id": penitipanBantuanId,
|
"judul": judul,
|
||||||
"lokasi_penyaluran_id": lokasiPenyaluranId,
|
|
||||||
"petugas_desa_id": petugasDesaId,
|
|
||||||
"jumlah": jumlah,
|
|
||||||
"satuan": satuan,
|
|
||||||
"deskripsi": deskripsi,
|
"deskripsi": deskripsi,
|
||||||
|
"lokasi_penyaluran_id": lokasiPenyaluranId,
|
||||||
|
"petugas_id": petugasId,
|
||||||
"status": status,
|
"status": status,
|
||||||
"gambar_urls": gambarUrls == null
|
"alasan_penolakan": alasanPenolakan,
|
||||||
? null
|
"tanggal_penjadwalan": tanggalPenjadwalan?.toIso8601String(),
|
||||||
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
"tanggal_penyaluran": tanggalPenyaluran?.toIso8601String(),
|
||||||
"tanggal_penyaluran": tanggalPenyaluran.toIso8601String(),
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"created_at": createdAt.toIso8601String(),
|
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,31 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class StokBantuanModel {
|
class StokBantuanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String bentukBantuanId; // Referensi ke BentukBantuan
|
final String? nama;
|
||||||
final double jumlahMasuk;
|
final String? bentukBantuanId;
|
||||||
final double jumlahKeluar;
|
final String? sumberBantuanId;
|
||||||
final double stokSisa;
|
final double? jumlah;
|
||||||
final String? satuan;
|
final String? satuan;
|
||||||
final String? catatan;
|
final String? deskripsi;
|
||||||
final DateTime tanggalUpdate;
|
final String? status;
|
||||||
final DateTime createdAt;
|
final DateTime? tanggalMasuk;
|
||||||
|
final DateTime? tanggalKadaluarsa;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
StokBantuanModel({
|
StokBantuanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.bentukBantuanId,
|
this.nama,
|
||||||
required this.jumlahMasuk,
|
this.bentukBantuanId,
|
||||||
required this.jumlahKeluar,
|
this.sumberBantuanId,
|
||||||
required this.stokSisa,
|
this.jumlah,
|
||||||
this.satuan,
|
this.satuan,
|
||||||
this.catatan,
|
this.deskripsi,
|
||||||
required this.tanggalUpdate,
|
this.status,
|
||||||
required this.createdAt,
|
this.tanggalMasuk,
|
||||||
|
this.tanggalKadaluarsa,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -33,29 +37,39 @@ class StokBantuanModel {
|
|||||||
factory StokBantuanModel.fromJson(Map<String, dynamic> json) =>
|
factory StokBantuanModel.fromJson(Map<String, dynamic> json) =>
|
||||||
StokBantuanModel(
|
StokBantuanModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
|
nama: json["nama"],
|
||||||
bentukBantuanId: json["bentuk_bantuan_id"],
|
bentukBantuanId: json["bentuk_bantuan_id"],
|
||||||
jumlahMasuk: json["jumlah_masuk"].toDouble(),
|
sumberBantuanId: json["sumber_bantuan_id"],
|
||||||
jumlahKeluar: json["jumlah_keluar"].toDouble(),
|
jumlah: json["jumlah"] != null ? json["jumlah"].toDouble() : 0.0,
|
||||||
stokSisa: json["stok_sisa"].toDouble(),
|
|
||||||
satuan: json["satuan"],
|
satuan: json["satuan"],
|
||||||
catatan: json["catatan"],
|
deskripsi: json["deskripsi"],
|
||||||
tanggalUpdate: DateTime.parse(json["tanggal_update"]),
|
status: json["status"],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
tanggalMasuk: json["tanggal_masuk"] != null
|
||||||
updatedAt: json["updated_at"] == null
|
? DateTime.parse(json["tanggal_masuk"])
|
||||||
? null
|
: null,
|
||||||
: DateTime.parse(json["updated_at"]),
|
tanggalKadaluarsa: json["tanggal_kadaluarsa"] != null
|
||||||
|
? DateTime.parse(json["tanggal_kadaluarsa"])
|
||||||
|
: null,
|
||||||
|
createdAt: json["created_at"] != null
|
||||||
|
? DateTime.parse(json["created_at"])
|
||||||
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
|
"nama": nama,
|
||||||
"bentuk_bantuan_id": bentukBantuanId,
|
"bentuk_bantuan_id": bentukBantuanId,
|
||||||
"jumlah_masuk": jumlahMasuk,
|
"sumber_bantuan_id": sumberBantuanId,
|
||||||
"jumlah_keluar": jumlahKeluar,
|
"jumlah": jumlah,
|
||||||
"stok_sisa": stokSisa,
|
|
||||||
"satuan": satuan,
|
"satuan": satuan,
|
||||||
"catatan": catatan,
|
"deskripsi": deskripsi,
|
||||||
"tanggal_update": tanggalUpdate.toIso8601String(),
|
"status": status,
|
||||||
"created_at": createdAt.toIso8601String(),
|
"tanggal_masuk": tanggalMasuk?.toIso8601String(),
|
||||||
|
"tanggal_kadaluarsa": tanggalKadaluarsa?.toIso8601String(),
|
||||||
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
class TindakanPengaduanModel {
|
class TindakanPengaduanModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String pengaduanId; // Referensi ke Pengaduan
|
final String? pengaduanId;
|
||||||
final String? userId; // Pengguna yang melakukan tindakan
|
final String? tindakan;
|
||||||
final String tindakan; // Deskripsi tindakan yang dilakukan
|
|
||||||
final String status; // Contoh: 'diproses', 'selesai'
|
|
||||||
final String? catatan;
|
final String? catatan;
|
||||||
final List<String>? gambarUrls; // URL gambar bukti tindakan
|
final String? status;
|
||||||
final DateTime tanggalTindakan;
|
final String? petugasId;
|
||||||
final DateTime createdAt;
|
final DateTime? tanggalTindakan;
|
||||||
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
TindakanPengaduanModel({
|
TindakanPengaduanModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.pengaduanId,
|
this.pengaduanId,
|
||||||
this.userId,
|
this.tindakan,
|
||||||
required this.tindakan,
|
|
||||||
required this.status,
|
|
||||||
this.catatan,
|
this.catatan,
|
||||||
this.gambarUrls,
|
this.status,
|
||||||
required this.tanggalTindakan,
|
this.petugasId,
|
||||||
required this.createdAt,
|
this.tanggalTindakan,
|
||||||
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,32 +32,30 @@ class TindakanPengaduanModel {
|
|||||||
TindakanPengaduanModel(
|
TindakanPengaduanModel(
|
||||||
id: json["id"],
|
id: json["id"],
|
||||||
pengaduanId: json["pengaduan_id"],
|
pengaduanId: json["pengaduan_id"],
|
||||||
userId: json["user_id"],
|
|
||||||
tindakan: json["tindakan"],
|
tindakan: json["tindakan"],
|
||||||
status: json["status"],
|
|
||||||
catatan: json["catatan"],
|
catatan: json["catatan"],
|
||||||
gambarUrls: json["gambar_urls"] == null
|
status: json["status"],
|
||||||
? null
|
petugasId: json["petugas_id"],
|
||||||
: List<String>.from(json["gambar_urls"].map((x) => x)),
|
tanggalTindakan: json["tanggal_tindakan"] != null
|
||||||
tanggalTindakan: DateTime.parse(json["tanggal_tindakan"]),
|
? DateTime.parse(json["tanggal_tindakan"])
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
: null,
|
||||||
updatedAt: json["updated_at"] == null
|
createdAt: json["created_at"] != null
|
||||||
? null
|
? DateTime.parse(json["created_at"])
|
||||||
: DateTime.parse(json["updated_at"]),
|
: null,
|
||||||
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
"id": id,
|
||||||
"pengaduan_id": pengaduanId,
|
"pengaduan_id": pengaduanId,
|
||||||
"user_id": userId,
|
|
||||||
"tindakan": tindakan,
|
"tindakan": tindakan,
|
||||||
"status": status,
|
|
||||||
"catatan": catatan,
|
"catatan": catatan,
|
||||||
"gambar_urls": gambarUrls == null
|
"status": status,
|
||||||
? null
|
"petugas_id": petugasId,
|
||||||
: List<dynamic>.from(gambarUrls!.map((x) => x)),
|
"tanggal_tindakan": tanggalTindakan?.toIso8601String(),
|
||||||
"tanggal_tindakan": tanggalTindakan.toIso8601String(),
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"created_at": createdAt.toIso8601String(),
|
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:penyaluran_app/app/data/models/desa_model.dart';
|
||||||
|
|
||||||
class UserModel {
|
class UserModel {
|
||||||
final String id;
|
final String id;
|
||||||
final String email;
|
final String email;
|
||||||
@ -5,6 +7,8 @@ class UserModel {
|
|||||||
final String? avatar;
|
final String? avatar;
|
||||||
final String role;
|
final String role;
|
||||||
final bool isActive;
|
final bool isActive;
|
||||||
|
final DesaModel? desa;
|
||||||
|
final String? desaId;
|
||||||
final DateTime? lastLogin;
|
final DateTime? lastLogin;
|
||||||
final DateTime? createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
@ -16,27 +20,41 @@ class UserModel {
|
|||||||
this.avatar,
|
this.avatar,
|
||||||
required this.role,
|
required this.role,
|
||||||
this.isActive = true,
|
this.isActive = true,
|
||||||
|
this.desa,
|
||||||
|
this.desaId,
|
||||||
this.lastLogin,
|
this.lastLogin,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['id'] == null || json['email'] == null) {
|
||||||
|
throw Exception('UserModel: id dan email tidak boleh null');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse desa jika ada
|
||||||
|
DesaModel? desaModel;
|
||||||
|
if (json['desa'] != null && json['desa'] is Map<String, dynamic>) {
|
||||||
|
desaModel = DesaModel.fromJson(json['desa'] as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
return UserModel(
|
return UserModel(
|
||||||
id: json['id'],
|
id: json['id'],
|
||||||
email: json['email'],
|
email: json['email'],
|
||||||
name: json['name'],
|
name: json['name'],
|
||||||
avatar: json['avatar'],
|
avatar: json['avatar'],
|
||||||
|
desa: desaModel,
|
||||||
|
desaId: json['desa_id'],
|
||||||
role: json['role'] ?? 'WARGA',
|
role: json['role'] ?? 'WARGA',
|
||||||
isActive: json['is_active'] ?? true,
|
isActive: json['is_active'] ?? true,
|
||||||
lastLogin: json['last_login'] != null
|
lastLogin: json['last_login'] != null
|
||||||
? DateTime.parse(json['last_login'])
|
? DateTime.parse(json['last_login'])
|
||||||
: null,
|
: null,
|
||||||
createdAt: json['CREATED_AT'] != null
|
createdAt: json['created_at'] != null
|
||||||
? DateTime.parse(json['CREATED_AT'])
|
? DateTime.parse(json['created_at'])
|
||||||
: null,
|
: null,
|
||||||
updatedAt: json['UPDATED_AT'] != null
|
updatedAt: json['updated_at'] != null
|
||||||
? DateTime.parse(json['UPDATED_AT'])
|
? DateTime.parse(json['updated_at'])
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -47,11 +65,13 @@ class UserModel {
|
|||||||
'email': email,
|
'email': email,
|
||||||
'name': name,
|
'name': name,
|
||||||
'avatar': avatar,
|
'avatar': avatar,
|
||||||
|
'desa_id': desaId,
|
||||||
|
'desa': desa?.toJson(),
|
||||||
'role': role,
|
'role': role,
|
||||||
'is_active': isActive,
|
'is_active': isActive,
|
||||||
'last_login': lastLogin?.toIso8601String(),
|
'last_login': lastLogin?.toIso8601String(),
|
||||||
'CREATED_AT': createdAt?.toIso8601String(),
|
'created_at': createdAt?.toIso8601String(),
|
||||||
'UPDATED_AT': updatedAt?.toIso8601String(),
|
'updated_at': updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +80,8 @@ class UserModel {
|
|||||||
String? email,
|
String? email,
|
||||||
String? name,
|
String? name,
|
||||||
String? avatar,
|
String? avatar,
|
||||||
|
DesaModel? desa,
|
||||||
|
String? desaId,
|
||||||
String? role,
|
String? role,
|
||||||
bool? isActive,
|
bool? isActive,
|
||||||
DateTime? lastLogin,
|
DateTime? lastLogin,
|
||||||
@ -71,6 +93,8 @@ class UserModel {
|
|||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
avatar: avatar ?? this.avatar,
|
avatar: avatar ?? this.avatar,
|
||||||
|
desa: desa ?? this.desa,
|
||||||
|
desaId: desaId ?? this.desaId,
|
||||||
role: role ?? this.role,
|
role: role ?? this.role,
|
||||||
isActive: isActive ?? this.isActive,
|
isActive: isActive ?? this.isActive,
|
||||||
lastLogin: lastLogin ?? this.lastLogin,
|
lastLogin: lastLogin ?? this.lastLogin,
|
||||||
@ -79,3 +103,43 @@ class UserModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? email;
|
||||||
|
final String? phone;
|
||||||
|
final String? role;
|
||||||
|
final String? token;
|
||||||
|
|
||||||
|
User({
|
||||||
|
this.id,
|
||||||
|
this.name,
|
||||||
|
this.email,
|
||||||
|
this.phone,
|
||||||
|
this.role,
|
||||||
|
this.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory User.fromJson(Map<String, dynamic> json) {
|
||||||
|
return User(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
email: json['email'],
|
||||||
|
phone: json['phone'],
|
||||||
|
role: json['role'],
|
||||||
|
token: json['token'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'email': email,
|
||||||
|
'phone': phone,
|
||||||
|
'role': role,
|
||||||
|
'token': token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,35 +2,39 @@ import 'dart:convert';
|
|||||||
|
|
||||||
// warga == penerima bantuan
|
// warga == penerima bantuan
|
||||||
class WargaModel {
|
class WargaModel {
|
||||||
final String id;
|
final String? id;
|
||||||
final String nama;
|
final String? nama;
|
||||||
final String? nik;
|
final String? nik;
|
||||||
final String? alamat;
|
final String? alamat;
|
||||||
final String? desa;
|
final String? desa;
|
||||||
final String? kecamatan;
|
final String? kecamatan;
|
||||||
final String? kabupaten;
|
final String? kabupaten;
|
||||||
final String? provinsi;
|
final String? provinsi;
|
||||||
final String? noTelp;
|
final String? telepon;
|
||||||
|
final String? email;
|
||||||
|
final String? catatan;
|
||||||
final String? kategori; // Contoh: 'lansia', 'disabilitas', 'miskin', dll
|
final String? kategori; // Contoh: 'lansia', 'disabilitas', 'miskin', dll
|
||||||
final String? status; // Contoh: 'aktif', 'nonaktif'
|
final String? status; // Contoh: 'AKTIF', 'NONAKTIF'
|
||||||
final String? lokasiPenyaluranId; // Referensi ke LokasiPenyaluran
|
final String? lokasiPenyaluranId; // Referensi ke LokasiPenyaluran
|
||||||
final DateTime createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
|
|
||||||
WargaModel({
|
WargaModel({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.nama,
|
this.nama,
|
||||||
this.nik,
|
this.nik,
|
||||||
this.alamat,
|
this.alamat,
|
||||||
this.desa,
|
this.desa,
|
||||||
this.kecamatan,
|
this.kecamatan,
|
||||||
this.kabupaten,
|
this.kabupaten,
|
||||||
this.provinsi,
|
this.provinsi,
|
||||||
this.noTelp,
|
this.telepon,
|
||||||
|
this.email,
|
||||||
|
this.catatan,
|
||||||
this.kategori,
|
this.kategori,
|
||||||
this.status,
|
this.status,
|
||||||
this.lokasiPenyaluranId,
|
this.lokasiPenyaluranId,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,14 +52,18 @@ class WargaModel {
|
|||||||
kecamatan: json["kecamatan"],
|
kecamatan: json["kecamatan"],
|
||||||
kabupaten: json["kabupaten"],
|
kabupaten: json["kabupaten"],
|
||||||
provinsi: json["provinsi"],
|
provinsi: json["provinsi"],
|
||||||
noTelp: json["no_telp"],
|
telepon: json["telepon"] ?? json["no_telp"],
|
||||||
|
email: json["email"],
|
||||||
|
catatan: json["catatan"],
|
||||||
kategori: json["kategori"],
|
kategori: json["kategori"],
|
||||||
status: json["status"],
|
status: json["status"],
|
||||||
lokasiPenyaluranId: json["lokasi_penyaluran_id"],
|
lokasiPenyaluranId: json["lokasi_penyaluran_id"],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
createdAt: json["created_at"] != null
|
||||||
updatedAt: json["updated_at"] == null
|
? DateTime.parse(json["created_at"])
|
||||||
? null
|
: null,
|
||||||
: DateTime.parse(json["updated_at"]),
|
updatedAt: json["updated_at"] != null
|
||||||
|
? DateTime.parse(json["updated_at"])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@ -67,11 +75,13 @@ class WargaModel {
|
|||||||
"kecamatan": kecamatan,
|
"kecamatan": kecamatan,
|
||||||
"kabupaten": kabupaten,
|
"kabupaten": kabupaten,
|
||||||
"provinsi": provinsi,
|
"provinsi": provinsi,
|
||||||
"no_telp": noTelp,
|
"telepon": telepon,
|
||||||
|
"email": email,
|
||||||
|
"catatan": catatan,
|
||||||
"kategori": kategori,
|
"kategori": kategori,
|
||||||
"status": status,
|
"status": status,
|
||||||
"lokasi_penyaluran_id": lokasiPenyaluranId,
|
"lokasi_penyaluran_id": lokasiPenyaluranId,
|
||||||
"created_at": createdAt.toIso8601String(),
|
"created_at": createdAt?.toIso8601String(),
|
||||||
"updated_at": updatedAt?.toIso8601String(),
|
"updated_at": updatedAt?.toIso8601String(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,65 +4,43 @@ import 'package:penyaluran_app/app/data/models/user_model.dart';
|
|||||||
class AuthProvider {
|
class AuthProvider {
|
||||||
final SupabaseService _supabaseService = SupabaseService.to;
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
// Metode untuk mendaftar pengguna baru
|
// Cache untuk menyimpan data profil pengguna
|
||||||
Future<UserModel?> signUp(String email, String password) async {
|
UserModel? _cachedUser;
|
||||||
try {
|
|
||||||
final response = await _supabaseService.signUp(email, password);
|
|
||||||
|
|
||||||
if (response.user != null) {
|
|
||||||
// Tunggu beberapa saat agar trigger di database berjalan
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
|
|
||||||
// Ambil profil pengguna dari database
|
|
||||||
final profileData = await _supabaseService.getUserProfile();
|
|
||||||
|
|
||||||
if (profileData != null) {
|
|
||||||
return UserModel.fromJson({
|
|
||||||
...profileData,
|
|
||||||
'id': response.user!.id,
|
|
||||||
'email': response.user!.email!,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jika profil belum tersedia, gunakan data default
|
|
||||||
return UserModel(
|
|
||||||
id: response.user!.id,
|
|
||||||
email: response.user!.email!,
|
|
||||||
role: 'WARGA', // Default role
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk login
|
// Metode untuk login
|
||||||
Future<UserModel?> signIn(String email, String password) async {
|
Future<UserModel?> signIn(String email, String password) async {
|
||||||
try {
|
try {
|
||||||
final response = await _supabaseService.signIn(email, password);
|
final response = await _supabaseService.signIn(email, password);
|
||||||
|
|
||||||
if (response.user != null) {
|
if (response.user != null && response.user?.email != null) {
|
||||||
// Ambil profil pengguna dari database
|
// Ambil profil pengguna dari database
|
||||||
final profileData = await _supabaseService.getUserProfile();
|
final profileData = await _supabaseService.getUserProfile();
|
||||||
|
print('DEBUG: Profile data dari signIn: $profileData');
|
||||||
|
|
||||||
if (profileData != null) {
|
if (profileData != null) {
|
||||||
return UserModel.fromJson({
|
// Buat UserModel dengan data yang ada
|
||||||
|
_cachedUser = UserModel.fromJson({
|
||||||
...profileData,
|
...profileData,
|
||||||
'id': response.user!.id,
|
'id': response.user!.id,
|
||||||
'email': response.user!.email!,
|
'email': response.user!.email!,
|
||||||
});
|
});
|
||||||
|
print(
|
||||||
|
'DEBUG: User model dibuat: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}');
|
||||||
|
return _cachedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika profil belum tersedia, gunakan data default
|
// Jika profil belum tersedia, gunakan data default
|
||||||
return UserModel(
|
_cachedUser = UserModel(
|
||||||
id: response.user!.id,
|
id: response.user!.id,
|
||||||
email: response.user!.email!,
|
email: response.user!.email!,
|
||||||
role: 'WARGA', // Default role
|
role: 'WARGA', // Default role
|
||||||
);
|
);
|
||||||
|
print('DEBUG: User model default dibuat: ${_cachedUser?.email}');
|
||||||
|
return _cachedUser;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('Error pada signIn: $e');
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +49,7 @@ class AuthProvider {
|
|||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
try {
|
try {
|
||||||
await _supabaseService.signOut();
|
await _supabaseService.signOut();
|
||||||
|
_cachedUser = null; // Hapus cache saat logout
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@ -78,25 +57,50 @@ class AuthProvider {
|
|||||||
|
|
||||||
// Metode untuk mendapatkan user saat ini
|
// Metode untuk mendapatkan user saat ini
|
||||||
Future<UserModel?> getCurrentUser() async {
|
Future<UserModel?> getCurrentUser() async {
|
||||||
|
// Jika ada cache dan user masih terautentikasi, gunakan cache
|
||||||
|
if (_cachedUser != null && _supabaseService.isAuthenticated) {
|
||||||
|
print(
|
||||||
|
'DEBUG: Menggunakan data user dari cache: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}');
|
||||||
|
return _cachedUser;
|
||||||
|
}
|
||||||
|
|
||||||
final user = _supabaseService.currentUser;
|
final user = _supabaseService.currentUser;
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
try {
|
||||||
// Ambil profil pengguna dari database
|
// Ambil profil pengguna dari database
|
||||||
final profileData = await _supabaseService.getUserProfile();
|
final profileData = await _supabaseService.getUserProfile();
|
||||||
|
print('DEBUG: Profile data dari getCurrentUser: $profileData');
|
||||||
|
|
||||||
if (profileData != null) {
|
if (profileData != null) {
|
||||||
return UserModel.fromJson({
|
// Buat UserModel dengan data yang ada
|
||||||
|
_cachedUser = UserModel.fromJson({
|
||||||
...profileData,
|
...profileData,
|
||||||
'id': user.id,
|
'id': user.id,
|
||||||
'email': user.email!,
|
'email': user.email!,
|
||||||
});
|
});
|
||||||
|
print(
|
||||||
|
'DEBUG: User model dibuat: ${_cachedUser?.name}, desa: ${_cachedUser?.desa?.nama}');
|
||||||
|
return _cachedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika profil belum tersedia, gunakan data default
|
// Jika profil belum tersedia, gunakan data default
|
||||||
return UserModel(
|
_cachedUser = UserModel(
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email!,
|
email: user.email!,
|
||||||
role: 'WARGA', // Default role
|
role: 'WARGA', // Default role
|
||||||
);
|
);
|
||||||
|
print('DEBUG: User model default dibuat: ${_cachedUser?.email}');
|
||||||
|
return _cachedUser;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error pada getCurrentUser: $e');
|
||||||
|
// Jika terjadi error, kembalikan model dengan data minimal
|
||||||
|
_cachedUser = UserModel(
|
||||||
|
id: user.id,
|
||||||
|
email: user.email!,
|
||||||
|
role: 'WARGA', // Default role
|
||||||
|
);
|
||||||
|
return _cachedUser;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -132,6 +136,9 @@ class AuthProvider {
|
|||||||
tanggalLahir: tanggalLahir,
|
tanggalLahir: tanggalLahir,
|
||||||
agama: agama,
|
agama: agama,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Invalidasi cache setelah membuat profil baru
|
||||||
|
_cachedUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan notifikasi pengguna
|
// Metode untuk mendapatkan notifikasi pengguna
|
||||||
|
@ -15,6 +15,9 @@ class AuthController extends GetxController {
|
|||||||
final RxBool isLoading = false.obs;
|
final RxBool isLoading = false.obs;
|
||||||
final RxBool isWargaProfileComplete = false.obs;
|
final RxBool isWargaProfileComplete = false.obs;
|
||||||
|
|
||||||
|
// Flag untuk menandai apakah sudah melakukan pengambilan data profil
|
||||||
|
final RxBool _hasLoadedProfile = false.obs;
|
||||||
|
|
||||||
// Form controllers
|
// Form controllers
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
final TextEditingController passwordController = TextEditingController();
|
final TextEditingController passwordController = TextEditingController();
|
||||||
@ -61,41 +64,76 @@ class AuthController extends GetxController {
|
|||||||
|
|
||||||
// Memeriksa status autentikasi
|
// Memeriksa status autentikasi
|
||||||
Future<void> checkAuthStatus() async {
|
Future<void> checkAuthStatus() async {
|
||||||
|
if (isLoading.value) {
|
||||||
|
return; // Hindari pemanggilan berulang jika sedang loading
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
print('Memeriksa status autentikasi...');
|
||||||
|
|
||||||
|
// Jika user sudah ada di memori dan profil sudah diambil, gunakan data yang ada
|
||||||
|
if (_user.value != null && _hasLoadedProfile.value) {
|
||||||
|
print('Menggunakan data user yang sudah ada di memori');
|
||||||
|
_handleAuthenticatedUser(_user.value!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika belum ada data user, ambil dari provider
|
||||||
final currentUser = await _authProvider.getCurrentUser();
|
final currentUser = await _authProvider.getCurrentUser();
|
||||||
|
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
|
print(
|
||||||
|
'User terautentikasi: ${currentUser.email}, role: ${currentUser.role}');
|
||||||
_user.value = currentUser;
|
_user.value = currentUser;
|
||||||
|
_hasLoadedProfile.value = true;
|
||||||
// Periksa apakah profil warga sudah lengkap
|
_handleAuthenticatedUser(currentUser);
|
||||||
await checkWargaProfileStatus();
|
|
||||||
|
|
||||||
// Hindari navigasi jika sudah berada di halaman yang sesuai
|
|
||||||
final currentRoute = Get.currentRoute;
|
|
||||||
|
|
||||||
// Untuk semua role, arahkan ke dashboard masing-masing
|
|
||||||
final targetRoute = _getTargetRouteForRole(currentUser.role);
|
|
||||||
if (currentRoute != targetRoute) {
|
|
||||||
navigateBasedOnRole(currentUser.role);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Jika tidak ada user yang login, arahkan ke halaman login
|
print('Tidak ada user yang terautentikasi');
|
||||||
if (Get.currentRoute != Routes.login) {
|
_handleUnauthenticatedUser();
|
||||||
// Bersihkan dependensi form sebelum navigasi
|
|
||||||
clearFormDependencies();
|
|
||||||
Get.offAllNamed(Routes.login);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error checking auth status: $e');
|
print('Error checking auth status: $e');
|
||||||
// Jika terjadi error, arahkan ke halaman login
|
print('Stack trace: ${StackTrace.current}');
|
||||||
|
_handleUnauthenticatedUser();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
print('Pemeriksaan status autentikasi selesai');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menangani user yang terautentikasi
|
||||||
|
void _handleAuthenticatedUser(UserModel user) {
|
||||||
|
// Hindari navigasi jika sudah berada di halaman yang sesuai
|
||||||
|
final currentRoute = Get.currentRoute;
|
||||||
|
print('Rute saat ini: $currentRoute');
|
||||||
|
|
||||||
|
// Pastikan role tidak null, gunakan default jika null
|
||||||
|
final role = user.role.isNotEmpty ? user.role : 'WARGA';
|
||||||
|
print('Role yang digunakan: $role');
|
||||||
|
|
||||||
|
// Untuk semua role, arahkan ke dashboard masing-masing
|
||||||
|
final targetRoute = _getTargetRouteForRole(role);
|
||||||
|
print('Target rute: $targetRoute');
|
||||||
|
|
||||||
|
if (currentRoute != targetRoute) {
|
||||||
|
print('Navigasi ke rute target berdasarkan role');
|
||||||
|
navigateBasedOnRole(role);
|
||||||
|
} else {
|
||||||
|
print('Sudah berada di rute yang sesuai, tidak perlu navigasi');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk menangani user yang tidak terautentikasi
|
||||||
|
void _handleUnauthenticatedUser() {
|
||||||
|
// Jika tidak ada user yang login, arahkan ke halaman login
|
||||||
if (Get.currentRoute != Routes.login) {
|
if (Get.currentRoute != Routes.login) {
|
||||||
|
print('Navigasi ke halaman login');
|
||||||
// Bersihkan dependensi form sebelum navigasi
|
// Bersihkan dependensi form sebelum navigasi
|
||||||
clearFormDependencies();
|
clearFormDependencies();
|
||||||
Get.offAllNamed(Routes.login);
|
Get.offAllNamed(Routes.login);
|
||||||
}
|
} else {
|
||||||
} finally {
|
print('Sudah berada di halaman login');
|
||||||
isLoading.value = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,28 +192,58 @@ class AuthController extends GetxController {
|
|||||||
|
|
||||||
// Metode untuk login
|
// Metode untuk login
|
||||||
Future<void> login() async {
|
Future<void> login() async {
|
||||||
if (!loginFormKey.currentState!.validate()) return;
|
print('DEBUG: Memulai proses login');
|
||||||
|
|
||||||
|
if (loginFormKey.currentState == null) {
|
||||||
|
print('Error: loginFormKey.currentState adalah null');
|
||||||
|
print('DEBUG: Form key: $loginFormKey');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Terjadi kesalahan pada form login. Silakan coba lagi.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('DEBUG: Form state ditemukan, melakukan validasi');
|
||||||
|
if (!loginFormKey.currentState!.validate()) {
|
||||||
|
print('DEBUG: Validasi form gagal');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Simpan nilai dari controller sebelum melakukan operasi asinkron
|
// Simpan nilai dari controller sebelum melakukan operasi asinkron
|
||||||
final email = emailController.text.trim();
|
final email = emailController.text.trim();
|
||||||
final password = passwordController.text;
|
final password = passwordController.text;
|
||||||
|
print('DEBUG: Email: $email, Password length: ${password.length}');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
print('DEBUG: Mengatur isLoading ke true');
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
|
|
||||||
|
print('DEBUG: Memanggil _authProvider.signIn');
|
||||||
final user = await _authProvider.signIn(
|
final user = await _authProvider.signIn(
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
print('DEBUG: Hasil signIn: ${user != null ? 'Berhasil' : 'Gagal'}');
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
print('DEBUG: User ditemukan, role: ${user.role}');
|
||||||
_user.value = user;
|
_user.value = user;
|
||||||
|
_hasLoadedProfile.value = true; // Tandai bahwa profil sudah diambil
|
||||||
clearControllers();
|
clearControllers();
|
||||||
|
|
||||||
// Arahkan ke dashboard sesuai peran
|
// Arahkan ke dashboard sesuai peran
|
||||||
|
print('DEBUG: Navigasi berdasarkan peran: ${user.role}');
|
||||||
navigateBasedOnRole(user.role);
|
navigateBasedOnRole(user.role);
|
||||||
|
} else {
|
||||||
|
print('DEBUG: User null setelah login berhasil');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error login: $e');
|
print('DEBUG: Error detail pada login: $e');
|
||||||
|
print('DEBUG: Stack trace: ${StackTrace.current}');
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Error',
|
'Error',
|
||||||
'Login gagal: ${e.toString()}',
|
'Login gagal: ${e.toString()}',
|
||||||
@ -184,6 +252,7 @@ class AuthController extends GetxController {
|
|||||||
colorText: Colors.white,
|
colorText: Colors.white,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
print('DEBUG: Mengatur isLoading ke false');
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,6 +262,7 @@ class AuthController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
await _authProvider.signOut();
|
await _authProvider.signOut();
|
||||||
_user.value = null;
|
_user.value = null;
|
||||||
|
_hasLoadedProfile.value = false; // Reset flag saat logout
|
||||||
isWargaProfileComplete.value = false;
|
isWargaProfileComplete.value = false;
|
||||||
|
|
||||||
// Bersihkan dependensi form sebelum navigasi
|
// Bersihkan dependensi form sebelum navigasi
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
|
||||||
class PenerimaBinding extends Bindings {
|
class PenerimaBinding extends Bindings {
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
|
// Pastikan AuthController tersedia
|
||||||
|
if (!Get.isRegistered<AuthController>()) {
|
||||||
|
Get.put(AuthController(), permanent: true);
|
||||||
|
}
|
||||||
|
|
||||||
Get.lazyPut<PenerimaController>(
|
Get.lazyPut<PenerimaController>(
|
||||||
() => PenerimaController(),
|
() => PenerimaController(),
|
||||||
fenix: true,
|
fenix: true,
|
||||||
|
@ -1,16 +1,61 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_dashboard_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/stok_bantuan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penitipan_bantuan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/pengaduan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/penerima_bantuan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/laporan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
|
||||||
class PetugasDesaBinding extends Bindings {
|
class PetugasDesaBinding extends Bindings {
|
||||||
@override
|
@override
|
||||||
void dependencies() {
|
void dependencies() {
|
||||||
|
// Pastikan AuthController tersedia
|
||||||
|
if (!Get.isRegistered<AuthController>()) {
|
||||||
|
Get.put(AuthController(), permanent: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main controller
|
||||||
Get.lazyPut<PetugasDesaController>(
|
Get.lazyPut<PetugasDesaController>(
|
||||||
() => PetugasDesaController(),
|
() => PetugasDesaController(),
|
||||||
fenix: true,
|
fenix: true,
|
||||||
);
|
);
|
||||||
Get.lazyPut<PenerimaController>(
|
|
||||||
() => PenerimaController(),
|
// Dashboard controller
|
||||||
|
Get.lazyPut<PetugasDesaDashboardController>(
|
||||||
|
() => PetugasDesaDashboardController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Jadwal penyaluran controller
|
||||||
|
Get.lazyPut<JadwalPenyaluranController>(
|
||||||
|
() => JadwalPenyaluranController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stok bantuan controller
|
||||||
|
Get.lazyPut<StokBantuanController>(
|
||||||
|
() => StokBantuanController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Penitipan bantuan controller
|
||||||
|
Get.lazyPut<PenitipanBantuanController>(
|
||||||
|
() => PenitipanBantuanController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pengaduan controller
|
||||||
|
Get.lazyPut<PengaduanController>(
|
||||||
|
() => PengaduanController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Penerima bantuan controller
|
||||||
|
Get.lazyPut<PenerimaBantuanController>(
|
||||||
|
() => PenerimaBantuanController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Laporan controller
|
||||||
|
Get.lazyPut<LaporanController>(
|
||||||
|
() => LaporanController(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,9 @@ class GreetingHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
'Kamu Login Sebagai $role${desa != null ? ' $desa' : ''}.',
|
desa != null && desa!.isNotEmpty
|
||||||
|
? 'Kamu Login Sebagai $role $desa.'
|
||||||
|
: 'Kamu Login Sebagai $role.',
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
|
|
||||||
class JadwalSectionWidget extends StatelessWidget {
|
class JadwalSectionWidget extends StatelessWidget {
|
||||||
final PetugasDesaController controller;
|
final JadwalPenyaluranController controller;
|
||||||
final String title;
|
final String title;
|
||||||
final List<Map<String, dynamic>> jadwalList;
|
final List<dynamic> jadwalList;
|
||||||
final String status;
|
final String status;
|
||||||
|
|
||||||
const JadwalSectionWidget({
|
const JadwalSectionWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.jadwalList,
|
required this.jadwalList,
|
||||||
required this.status,
|
required this.status,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -62,20 +62,24 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Map<String, dynamic>> _getCurrentJadwalList() {
|
List<dynamic> _getCurrentJadwalList() {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case 'Hari Ini':
|
case 'Hari Ini':
|
||||||
return controller.jadwalHariIni;
|
return controller.jadwalHariIni.toList();
|
||||||
case 'Mendatang':
|
case 'Mendatang':
|
||||||
return controller.jadwalMendatang;
|
return controller.jadwalMendatang.toList();
|
||||||
case 'Selesai':
|
case 'Selesai':
|
||||||
return controller.jadwalSelesai;
|
return controller.jadwalSelesai.toList();
|
||||||
default:
|
default:
|
||||||
return jadwalList;
|
return jadwalList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildJadwalItem(TextTheme textTheme, Map<String, dynamic> jadwal) {
|
Widget _buildJadwalItem(TextTheme textTheme, dynamic jadwal) {
|
||||||
|
// Konversi jadwal ke Map jika itu adalah PenyaluranBantuanModel
|
||||||
|
final Map<String, dynamic> jadwalData =
|
||||||
|
jadwal is Map<String, dynamic> ? jadwal : jadwal.toJson();
|
||||||
|
|
||||||
Color statusColor;
|
Color statusColor;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Aktif':
|
case 'Aktif':
|
||||||
@ -94,7 +98,7 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigasi ke halaman pelaksanaan penyaluran dengan data jadwal
|
// Navigasi ke halaman pelaksanaan penyaluran dengan data jadwal
|
||||||
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwal);
|
Get.toNamed(Routes.pelaksanaanPenyaluran, arguments: jadwalData);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@ -120,7 +124,7 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
jadwal['lokasi'] ?? '',
|
jadwalData['lokasi'] ?? '',
|
||||||
style: textTheme.titleMedium?.copyWith(
|
style: textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -144,23 +148,23 @@ class JadwalSectionWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Jenis Bantuan: ${jadwal['jenis_bantuan'] ?? ''}',
|
'Jenis Bantuan: ${jadwalData['jenis_bantuan'] ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Tanggal: ${jadwal['tanggal'] ?? ''}',
|
'Tanggal: ${jadwalData['tanggal'] ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Waktu: ${jadwal['waktu'] ?? ''}',
|
'Waktu: ${jadwalData['waktu'] ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
if (jadwal['jumlah_penerima'] != null) ...[
|
if (jadwalData['jumlah_penerima'] != null) ...[
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Jumlah Penerima: ${jadwal['jumlah_penerima']}',
|
'Jumlah Penerima: ${jadwalData['jumlah_penerima']}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
||||||
final PetugasDesaController controller;
|
final JadwalPenyaluranController controller;
|
||||||
|
|
||||||
const PermintaanPenjadwalanSummaryWidget({
|
const PermintaanPenjadwalanSummaryWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -134,8 +134,11 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPermintaanPreview(
|
Widget _buildPermintaanPreview(TextTheme textTheme, dynamic permintaan) {
|
||||||
TextTheme textTheme, Map<String, dynamic> permintaan) {
|
// Konversi permintaan ke Map jika itu adalah PenyaluranBantuanModel
|
||||||
|
final Map<String, dynamic> permintaanData =
|
||||||
|
permintaan is Map<String, dynamic> ? permintaan : permintaan.toJson();
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
@ -152,7 +155,7 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
permintaan['nama'] ?? '',
|
permintaanData['nama'] ?? '',
|
||||||
style: textTheme.titleSmall?.copyWith(
|
style: textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -178,12 +181,12 @@ class PermintaanPenjadwalanSummaryWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Jenis: ${permintaan['jenis_bantuan'] ?? ''}',
|
'Jenis: ${permintaanData['jenis_bantuan'] ?? ''}',
|
||||||
style: textTheme.bodySmall,
|
style: textTheme.bodySmall,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Tanggal: ${permintaan['tanggal_permintaan'] ?? ''}',
|
'Tanggal: ${permintaanData['tanggal_permintaan'] ?? ''}',
|
||||||
style: textTheme.bodySmall,
|
style: textTheme.bodySmall,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class PermintaanPenjadwalanWidget extends StatelessWidget {
|
class PermintaanPenjadwalanWidget extends StatelessWidget {
|
||||||
final PetugasDesaController controller;
|
final JadwalPenyaluranController controller;
|
||||||
|
|
||||||
const PermintaanPenjadwalanWidget({
|
const PermintaanPenjadwalanWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -90,7 +91,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// Widget untuk menampilkan item permintaan penjadwalan
|
// Widget untuk menampilkan item permintaan penjadwalan
|
||||||
Widget _buildPermintaanItem(
|
Widget _buildPermintaanItem(
|
||||||
TextTheme textTheme, Map<String, dynamic> permintaan) {
|
TextTheme textTheme, PenyaluranBantuanModel permintaan) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
margin: const EdgeInsets.only(bottom: 10),
|
||||||
@ -119,7 +120,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
permintaan['nama'] ?? '',
|
permintaan.judul ?? '',
|
||||||
style: textTheme.titleMedium?.copyWith(
|
style: textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -143,22 +144,22 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'NIK: ${permintaan['nik'] ?? ''}',
|
'ID: ${permintaan.id ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Jenis Bantuan: ${permintaan['jenis_bantuan'] ?? ''}',
|
'Jenis Bantuan: ${permintaan.judul ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Tanggal Permintaan: ${permintaan['tanggal_permintaan'] ?? ''}',
|
'Tanggal Permintaan: ${permintaan.createdAt?.toString().substring(0, 10) ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Alamat: ${permintaan['alamat'] ?? ''}',
|
'Deskripsi: ${permintaan.deskripsi ?? ''}',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@ -191,15 +192,15 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dialog untuk konfirmasi permintaan
|
// Dialog untuk konfirmasi permintaan
|
||||||
void _showKonfirmasiDialog(Map<String, dynamic> permintaan) {
|
void _showKonfirmasiDialog(PenyaluranBantuanModel permintaan) {
|
||||||
String? selectedJadwalId;
|
String? selectedJadwalId;
|
||||||
|
|
||||||
// Data jadwal yang tersedia dari controller
|
// Data jadwal yang tersedia dari controller
|
||||||
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: jadwal['id'],
|
value: jadwal.id,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${jadwal['tanggal']} - ${jadwal['lokasi']} (${jadwal['jenis_bantuan']})'),
|
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.judul ?? ''})'),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama']}.'),
|
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.judul}.'),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('Pilih jadwal penyaluran:'),
|
const Text('Pilih jadwal penyaluran:'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -245,9 +246,8 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (selectedJadwalId != null) {
|
if (selectedJadwalId != null) {
|
||||||
// Panggil metode konfirmasi di controller
|
// Panggil metode konfirmasi di controller
|
||||||
controller.konfirmasiPermintaanPenjadwalan(
|
controller.approveJadwal(
|
||||||
permintaan['id'],
|
permintaan.id ?? '',
|
||||||
selectedJadwalId!,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Get.back();
|
Get.back();
|
||||||
@ -279,7 +279,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dialog untuk menolak permintaan
|
// Dialog untuk menolak permintaan
|
||||||
void _showTolakDialog(Map<String, dynamic> permintaan) {
|
void _showTolakDialog(PenyaluranBantuanModel permintaan) {
|
||||||
final TextEditingController alasanController = TextEditingController();
|
final TextEditingController alasanController = TextEditingController();
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
@ -290,7 +290,7 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama']}.'),
|
'Anda akan menolak permintaan penjadwalan dari ${permintaan.judul}.'),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('Alasan penolakan:'),
|
const Text('Alasan penolakan:'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -313,8 +313,8 @@ class PermintaanPenjadwalanWidget extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (alasanController.text.trim().isNotEmpty) {
|
if (alasanController.text.trim().isNotEmpty) {
|
||||||
// Panggil metode tolak di controller
|
// Panggil metode tolak di controller
|
||||||
controller.tolakPermintaanPenjadwalan(
|
controller.rejectJadwal(
|
||||||
permintaan['id'],
|
permintaan.id ?? '',
|
||||||
alasanController.text.trim(),
|
alasanController.text.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -0,0 +1,187 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class JadwalPenyaluranController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk jadwal
|
||||||
|
final RxList<PenyaluranBantuanModel> jadwalHariIni =
|
||||||
|
<PenyaluranBantuanModel>[].obs;
|
||||||
|
final RxList<PenyaluranBantuanModel> jadwalMendatang =
|
||||||
|
<PenyaluranBantuanModel>[].obs;
|
||||||
|
final RxList<PenyaluranBantuanModel> jadwalSelesai =
|
||||||
|
<PenyaluranBantuanModel>[].obs;
|
||||||
|
|
||||||
|
// Data untuk permintaan penjadwalan
|
||||||
|
final RxList<PenyaluranBantuanModel> permintaanPenjadwalan =
|
||||||
|
<PenyaluranBantuanModel>[].obs;
|
||||||
|
final RxInt jumlahPermintaanPenjadwalan = 0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadJadwalData();
|
||||||
|
loadPermintaanPenjadwalanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadJadwalData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Mengambil data jadwal hari ini
|
||||||
|
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
||||||
|
if (jadwalHariIniData != null) {
|
||||||
|
jadwalHariIni.value = jadwalHariIniData
|
||||||
|
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mengambil data jadwal mendatang
|
||||||
|
final jadwalMendatangData = await _supabaseService.getJadwalMendatang();
|
||||||
|
if (jadwalMendatangData != null) {
|
||||||
|
jadwalMendatang.value = jadwalMendatangData
|
||||||
|
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mengambil data jadwal selesai
|
||||||
|
final jadwalSelesaiData = await _supabaseService.getJadwalSelesai();
|
||||||
|
if (jadwalSelesaiData != null) {
|
||||||
|
jadwalSelesai.value = jadwalSelesaiData
|
||||||
|
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading jadwal data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadPermintaanPenjadwalanData() async {
|
||||||
|
try {
|
||||||
|
final permintaanData = await _supabaseService.getPermintaanPenjadwalan();
|
||||||
|
if (permintaanData != null) {
|
||||||
|
permintaanPenjadwalan.value = permintaanData
|
||||||
|
.map((data) => PenyaluranBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading permintaan penjadwalan data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> approveJadwal(String jadwalId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.approveJadwal(jadwalId);
|
||||||
|
await loadPermintaanPenjadwalanData();
|
||||||
|
await loadJadwalData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Jadwal berhasil disetujui',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error approving jadwal: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menyetujui jadwal: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> rejectJadwal(String jadwalId, String alasan) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.rejectJadwal(jadwalId, alasan);
|
||||||
|
await loadPermintaanPenjadwalanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Jadwal berhasil ditolak',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error rejecting jadwal: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menolak jadwal: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completeJadwal(String jadwalId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.completeJadwal(jadwalId);
|
||||||
|
await loadJadwalData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Jadwal berhasil diselesaikan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error completing jadwal: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menyelesaikan jadwal: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadJadwalData();
|
||||||
|
await loadPermintaanPenjadwalanData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeCategory(int index) {
|
||||||
|
selectedCategoryIndex.value = index;
|
||||||
|
}
|
||||||
|
}
|
197
lib/app/modules/petugas_desa/controllers/laporan_controller.dart
Normal file
197
lib/app/modules/petugas_desa/controllers/laporan_controller.dart
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/laporan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class LaporanController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk laporan
|
||||||
|
final RxList<LaporanModel> daftarLaporan = <LaporanModel>[].obs;
|
||||||
|
|
||||||
|
// Filter tanggal
|
||||||
|
final Rx<DateTime?> tanggalMulai = Rx<DateTime?>(null);
|
||||||
|
final Rx<DateTime?> tanggalSelesai = Rx<DateTime?>(null);
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
// Set default tanggal filter (1 bulan terakhir)
|
||||||
|
tanggalSelesai.value = DateTime.now();
|
||||||
|
tanggalMulai.value = DateTime.now().subtract(const Duration(days: 30));
|
||||||
|
loadLaporanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadLaporanData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final laporanData = await _supabaseService.getLaporan(
|
||||||
|
tanggalMulai.value,
|
||||||
|
tanggalSelesai.value,
|
||||||
|
);
|
||||||
|
if (laporanData != null) {
|
||||||
|
daftarLaporan.value =
|
||||||
|
laporanData.map((data) => LaporanModel.fromJson(data)).toList();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading laporan data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> generateLaporan(String jenis) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final laporan = LaporanModel(
|
||||||
|
jenis: jenis,
|
||||||
|
tanggalMulai: tanggalMulai.value,
|
||||||
|
tanggalSelesai: tanggalSelesai.value,
|
||||||
|
petugasId: user?.id,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final laporanId =
|
||||||
|
await _supabaseService.generateLaporan(laporan.toJson());
|
||||||
|
|
||||||
|
if (laporanId != null) {
|
||||||
|
await loadLaporanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Laporan berhasil dibuat',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error generating laporan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal membuat laporan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> downloadLaporan(String laporanId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final url = await _supabaseService.downloadLaporan(laporanId);
|
||||||
|
if (url != null) {
|
||||||
|
// Implementasi download file
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Laporan berhasil diunduh',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error downloading laporan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal mengunduh laporan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteLaporan(String laporanId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.deleteLaporan(laporanId);
|
||||||
|
await loadLaporanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Laporan berhasil dihapus',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting laporan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menghapus laporan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTanggalMulai(DateTime tanggal) {
|
||||||
|
tanggalMulai.value = tanggal;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTanggalSelesai(DateTime tanggal) {
|
||||||
|
tanggalSelesai.value = tanggal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> applyFilter() async {
|
||||||
|
await loadLaporanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadLaporanData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeCategory(int index) {
|
||||||
|
selectedCategoryIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<LaporanModel> getFilteredLaporan() {
|
||||||
|
switch (selectedCategoryIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return daftarLaporan;
|
||||||
|
case 1:
|
||||||
|
return daftarLaporan
|
||||||
|
.where((item) => item.jenis == 'PENYALURAN')
|
||||||
|
.toList();
|
||||||
|
case 2:
|
||||||
|
return daftarLaporan
|
||||||
|
.where((item) => item.jenis == 'STOK_BANTUAN')
|
||||||
|
.toList();
|
||||||
|
case 3:
|
||||||
|
return daftarLaporan.where((item) => item.jenis == 'PENERIMA').toList();
|
||||||
|
default:
|
||||||
|
return daftarLaporan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,307 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/warga_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class PenerimaBantuanController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk penerima bantuan
|
||||||
|
final RxList<WargaModel> daftarPenerima = <WargaModel>[].obs;
|
||||||
|
final RxInt totalPenerima = 0.obs;
|
||||||
|
final RxInt totalPenerimaAktif = 0.obs;
|
||||||
|
final RxInt totalPenerimaNonaktif = 0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian dan form
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
final TextEditingController namaController = TextEditingController();
|
||||||
|
final TextEditingController nikController = TextEditingController();
|
||||||
|
final TextEditingController alamatController = TextEditingController();
|
||||||
|
final TextEditingController teleponController = TextEditingController();
|
||||||
|
final TextEditingController emailController = TextEditingController();
|
||||||
|
final TextEditingController catatanController = TextEditingController();
|
||||||
|
|
||||||
|
// Form key
|
||||||
|
final GlobalKey<FormState> penerimaFormKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadPenerimaData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
namaController.dispose();
|
||||||
|
nikController.dispose();
|
||||||
|
alamatController.dispose();
|
||||||
|
teleponController.dispose();
|
||||||
|
emailController.dispose();
|
||||||
|
catatanController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadPenerimaData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final penerimaData = await _supabaseService.getPenerimaBantuan();
|
||||||
|
if (penerimaData != null) {
|
||||||
|
daftarPenerima.value =
|
||||||
|
penerimaData.map((data) => WargaModel.fromJson(data)).toList();
|
||||||
|
|
||||||
|
// Hitung total
|
||||||
|
totalPenerima.value = daftarPenerima.length;
|
||||||
|
totalPenerimaAktif.value =
|
||||||
|
daftarPenerima.where((item) => item.status == 'AKTIF').length;
|
||||||
|
totalPenerimaNonaktif.value =
|
||||||
|
daftarPenerima.where((item) => item.status == 'NONAKTIF').length;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading penerima data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tambahPenerima() async {
|
||||||
|
if (!penerimaFormKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final penerima = WargaModel(
|
||||||
|
nama: namaController.text,
|
||||||
|
nik: nikController.text,
|
||||||
|
alamat: alamatController.text,
|
||||||
|
telepon: teleponController.text,
|
||||||
|
email: emailController.text,
|
||||||
|
catatan: catatanController.text,
|
||||||
|
status: 'AKTIF',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _supabaseService.tambahPenerima(penerima.toJson());
|
||||||
|
|
||||||
|
// Clear form
|
||||||
|
clearForm();
|
||||||
|
|
||||||
|
await loadPenerimaData();
|
||||||
|
Get.back(); // Close dialog
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penerima bantuan berhasil ditambahkan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding penerima: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menambahkan penerima bantuan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updatePenerima(String penerimaId) async {
|
||||||
|
if (!penerimaFormKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final penerima = WargaModel(
|
||||||
|
id: penerimaId,
|
||||||
|
nama: namaController.text,
|
||||||
|
nik: nikController.text,
|
||||||
|
alamat: alamatController.text,
|
||||||
|
telepon: teleponController.text,
|
||||||
|
email: emailController.text,
|
||||||
|
catatan: catatanController.text,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _supabaseService.updatePenerima(penerimaId, penerima.toJson());
|
||||||
|
|
||||||
|
// Clear form
|
||||||
|
clearForm();
|
||||||
|
|
||||||
|
await loadPenerimaData();
|
||||||
|
Get.back(); // Close dialog
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penerima bantuan berhasil diperbarui',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating penerima: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memperbarui penerima bantuan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> nonaktifkanPenerima(String penerimaId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.updateStatusPenerima(penerimaId, 'NONAKTIF');
|
||||||
|
await loadPenerimaData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penerima bantuan berhasil dinonaktifkan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deactivating penerima: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menonaktifkan penerima bantuan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> aktifkanPenerima(String penerimaId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.updateStatusPenerima(penerimaId, 'AKTIF');
|
||||||
|
await loadPenerimaData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penerima bantuan berhasil diaktifkan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error activating penerima: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal mengaktifkan penerima bantuan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFormData(WargaModel penerima) {
|
||||||
|
namaController.text = penerima.nama ?? '';
|
||||||
|
nikController.text = penerima.nik ?? '';
|
||||||
|
alamatController.text = penerima.alamat ?? '';
|
||||||
|
teleponController.text = penerima.telepon ?? '';
|
||||||
|
emailController.text = penerima.email ?? '';
|
||||||
|
catatanController.text = penerima.catatan ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearForm() {
|
||||||
|
namaController.clear();
|
||||||
|
nikController.clear();
|
||||||
|
alamatController.clear();
|
||||||
|
teleponController.clear();
|
||||||
|
emailController.clear();
|
||||||
|
catatanController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadPenerimaData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeCategory(int index) {
|
||||||
|
selectedCategoryIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WargaModel> getFilteredPenerima() {
|
||||||
|
switch (selectedCategoryIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return daftarPenerima;
|
||||||
|
case 1:
|
||||||
|
return daftarPenerima.where((item) => item.status == 'AKTIF').toList();
|
||||||
|
case 2:
|
||||||
|
return daftarPenerima
|
||||||
|
.where((item) => item.status == 'NONAKTIF')
|
||||||
|
.toList();
|
||||||
|
default:
|
||||||
|
return daftarPenerima;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi form
|
||||||
|
String? validateNama(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateNIK(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'NIK tidak boleh kosong';
|
||||||
|
}
|
||||||
|
if (value.length != 16) {
|
||||||
|
return 'NIK harus 16 digit';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateAlamat(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Alamat tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateTelepon(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nomor telepon tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? validateEmail(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return null; // Email boleh kosong
|
||||||
|
}
|
||||||
|
if (!GetUtils.isEmail(value)) {
|
||||||
|
return 'Email tidak valid';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
||||||
|
|
||||||
class PenerimaController extends GetxController {
|
class PenerimaController extends GetxController {
|
||||||
final RxList<Map<String, dynamic>> daftarPenerima =
|
final RxList<Map<String, dynamic>> daftarPenerima =
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/pengaduan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/tindakan_pengaduan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class PengaduanController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk pengaduan
|
||||||
|
final RxList<PengaduanModel> daftarPengaduan = <PengaduanModel>[].obs;
|
||||||
|
final RxInt jumlahDiproses = 0.obs;
|
||||||
|
final RxInt jumlahTindakan = 0.obs;
|
||||||
|
final RxInt jumlahSelesai = 0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian dan form
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
final TextEditingController tindakanController = TextEditingController();
|
||||||
|
final TextEditingController catatanController = TextEditingController();
|
||||||
|
|
||||||
|
// Form key
|
||||||
|
final GlobalKey<FormState> tindakanFormKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadPengaduanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
tindakanController.dispose();
|
||||||
|
catatanController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadPengaduanData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final pengaduanData = await _supabaseService.getPengaduan();
|
||||||
|
if (pengaduanData != null) {
|
||||||
|
daftarPengaduan.value =
|
||||||
|
pengaduanData.map((data) => PengaduanModel.fromJson(data)).toList();
|
||||||
|
|
||||||
|
// Hitung jumlah berdasarkan status
|
||||||
|
jumlahDiproses.value =
|
||||||
|
daftarPengaduan.where((item) => item.status == 'DIPROSES').length;
|
||||||
|
jumlahTindakan.value =
|
||||||
|
daftarPengaduan.where((item) => item.status == 'TINDAKAN').length;
|
||||||
|
jumlahSelesai.value =
|
||||||
|
daftarPengaduan.where((item) => item.status == 'SELESAI').length;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading pengaduan data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> prosesPengaduan(String pengaduanId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.prosesPengaduan(pengaduanId);
|
||||||
|
await loadPengaduanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Pengaduan berhasil diproses',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error processing pengaduan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memproses pengaduan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tambahTindakan(String pengaduanId) async {
|
||||||
|
if (!tindakanFormKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final tindakan = TindakanPengaduanModel(
|
||||||
|
pengaduanId: pengaduanId,
|
||||||
|
tindakan: tindakanController.text,
|
||||||
|
catatan: catatanController.text,
|
||||||
|
tanggalTindakan: DateTime.now(),
|
||||||
|
petugasId: user?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _supabaseService.tambahTindakanPengaduan(tindakan.toJson());
|
||||||
|
await _supabaseService.updateStatusPengaduan(pengaduanId, 'TINDAKAN');
|
||||||
|
|
||||||
|
// Clear form
|
||||||
|
tindakanController.clear();
|
||||||
|
catatanController.clear();
|
||||||
|
|
||||||
|
await loadPengaduanData();
|
||||||
|
Get.back(); // Close dialog
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Tindakan berhasil ditambahkan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding tindakan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menambahkan tindakan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selesaikanPengaduan(String pengaduanId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.updateStatusPengaduan(pengaduanId, 'SELESAI');
|
||||||
|
await loadPengaduanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Pengaduan berhasil diselesaikan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error completing pengaduan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menyelesaikan pengaduan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TindakanPengaduanModel>> getTindakanPengaduan(
|
||||||
|
String pengaduanId) async {
|
||||||
|
try {
|
||||||
|
final tindakanData =
|
||||||
|
await _supabaseService.getTindakanPengaduan(pengaduanId);
|
||||||
|
if (tindakanData != null) {
|
||||||
|
return tindakanData
|
||||||
|
.map((data) => TindakanPengaduanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting tindakan pengaduan: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadPengaduanData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeCategory(int index) {
|
||||||
|
selectedCategoryIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PengaduanModel> getFilteredPengaduan() {
|
||||||
|
switch (selectedCategoryIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return daftarPengaduan;
|
||||||
|
case 1:
|
||||||
|
return daftarPengaduan
|
||||||
|
.where((item) => item.status == 'DIPROSES')
|
||||||
|
.toList();
|
||||||
|
case 2:
|
||||||
|
return daftarPengaduan
|
||||||
|
.where((item) => item.status == 'TINDAKAN')
|
||||||
|
.toList();
|
||||||
|
case 3:
|
||||||
|
return daftarPengaduan
|
||||||
|
.where((item) => item.status == 'SELESAI')
|
||||||
|
.toList();
|
||||||
|
default:
|
||||||
|
return daftarPengaduan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi form
|
||||||
|
String? validateTindakan(String? value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Tindakan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,165 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/penitipan_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/donatur_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class PenitipanBantuanController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Indeks kategori yang dipilih untuk filter
|
||||||
|
final RxInt selectedCategoryIndex = 0.obs;
|
||||||
|
|
||||||
|
// Data untuk penitipan
|
||||||
|
final RxList<PenitipanBantuanModel> daftarPenitipan =
|
||||||
|
<PenitipanBantuanModel>[].obs;
|
||||||
|
final RxInt jumlahMenunggu = 0.obs;
|
||||||
|
final RxInt jumlahTerverifikasi = 0.obs;
|
||||||
|
final RxInt jumlahDitolak = 0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadPenitipanData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadPenitipanData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final penitipanData = await _supabaseService.getPenitipanBantuan();
|
||||||
|
if (penitipanData != null) {
|
||||||
|
daftarPenitipan.value = penitipanData
|
||||||
|
.map((data) => PenitipanBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Hitung jumlah berdasarkan status
|
||||||
|
jumlahMenunggu.value =
|
||||||
|
daftarPenitipan.where((item) => item.status == 'MENUNGGU').length;
|
||||||
|
jumlahTerverifikasi.value = daftarPenitipan
|
||||||
|
.where((item) => item.status == 'TERVERIFIKASI')
|
||||||
|
.length;
|
||||||
|
jumlahDitolak.value =
|
||||||
|
daftarPenitipan.where((item) => item.status == 'DITOLAK').length;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading penitipan data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> verifikasiPenitipan(String penitipanId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.verifikasiPenitipan(penitipanId);
|
||||||
|
await loadPenitipanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penitipan berhasil diverifikasi',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error verifying penitipan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memverifikasi penitipan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tolakPenitipan(String penitipanId, String alasan) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.tolakPenitipan(penitipanId, alasan);
|
||||||
|
await loadPenitipanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Penitipan berhasil ditolak',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error rejecting penitipan: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menolak penitipan: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DonaturModel?> getDonaturInfo(String donaturId) async {
|
||||||
|
try {
|
||||||
|
final donaturData = await _supabaseService.getDonaturById(donaturId);
|
||||||
|
if (donaturData != null) {
|
||||||
|
return DonaturModel.fromJson(donaturData);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting donatur info: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadPenitipanData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeCategory(int index) {
|
||||||
|
selectedCategoryIndex.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PenitipanBantuanModel> getFilteredPenitipan() {
|
||||||
|
switch (selectedCategoryIndex.value) {
|
||||||
|
case 0:
|
||||||
|
return daftarPenitipan;
|
||||||
|
case 1:
|
||||||
|
return daftarPenitipan
|
||||||
|
.where((item) => item.status == 'MENUNGGU')
|
||||||
|
.toList();
|
||||||
|
case 2:
|
||||||
|
return daftarPenitipan
|
||||||
|
.where((item) => item.status == 'TERVERIFIKASI')
|
||||||
|
.toList();
|
||||||
|
case 3:
|
||||||
|
return daftarPenitipan
|
||||||
|
.where((item) => item.status == 'DITOLAK')
|
||||||
|
.toList();
|
||||||
|
default:
|
||||||
|
return daftarPenitipan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/desa_model.dart';
|
||||||
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
@ -8,81 +9,63 @@ class PetugasDesaController extends GetxController {
|
|||||||
final AuthController _authController = Get.find<AuthController>();
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
final SupabaseService _supabaseService = SupabaseService.to;
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
final RxBool isLoading = false.obs;
|
|
||||||
final Rx<Map<String, dynamic>?> roleData = Rx<Map<String, dynamic>?>(null);
|
|
||||||
|
|
||||||
// Indeks kategori yang dipilih untuk filter
|
|
||||||
final RxInt selectedCategoryIndex = 0.obs;
|
|
||||||
|
|
||||||
// Indeks tab yang aktif di bottom navigation bar
|
// Indeks tab yang aktif di bottom navigation bar
|
||||||
final RxInt activeTabIndex = 0.obs;
|
final RxInt activeTabIndex = 0.obs;
|
||||||
|
|
||||||
// Data untuk dashboard
|
|
||||||
final RxInt totalPenerima = 0.obs;
|
|
||||||
final RxInt totalBantuan = 0.obs;
|
|
||||||
final RxInt totalPenyaluran = 0.obs;
|
|
||||||
final RxDouble progressPenyaluran = 0.0.obs;
|
|
||||||
|
|
||||||
// Data untuk jadwal
|
|
||||||
final RxList<Map<String, dynamic>> jadwalHariIni =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxList<Map<String, dynamic>> jadwalMendatang =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxList<Map<String, dynamic>> jadwalSelesai =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
|
|
||||||
// Data untuk permintaan penjadwalan
|
|
||||||
final RxList<Map<String, dynamic>> permintaanPenjadwalan =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxInt jumlahPermintaanPenjadwalan = 0.obs;
|
|
||||||
|
|
||||||
// Data untuk notifikasi
|
|
||||||
final RxList<Map<String, dynamic>> notifikasiBelumDibaca =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
|
||||||
|
|
||||||
// Data untuk inventaris
|
|
||||||
final RxList<Map<String, dynamic>> daftarInventaris =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxDouble totalStok = 0.0.obs;
|
|
||||||
final RxDouble stokMasuk = 0.0.obs;
|
|
||||||
final RxDouble stokKeluar = 0.0.obs;
|
|
||||||
|
|
||||||
// Data untuk penitipan
|
|
||||||
final RxList<Map<String, dynamic>> daftarPenitipan =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxInt jumlahMenunggu = 0.obs;
|
|
||||||
final RxInt jumlahTerverifikasi = 0.obs;
|
|
||||||
final RxInt jumlahDitolak = 0.obs;
|
|
||||||
|
|
||||||
// Data untuk pengaduan
|
|
||||||
final RxList<Map<String, dynamic>> daftarPengaduan =
|
|
||||||
<Map<String, dynamic>>[].obs;
|
|
||||||
final RxInt jumlahDiproses = 0.obs;
|
|
||||||
final RxInt jumlahTindakan = 0.obs;
|
|
||||||
final RxInt jumlahSelesai = 0.obs;
|
|
||||||
|
|
||||||
// Controller untuk pencarian
|
// Controller untuk pencarian
|
||||||
final TextEditingController searchController = TextEditingController();
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
// Data profil pengguna dari cache
|
||||||
|
final RxMap<String, dynamic> userProfile = RxMap<String, dynamic>({});
|
||||||
|
|
||||||
|
// Model desa dari cache
|
||||||
|
final Rx<DesaModel?> desaModel = Rx<DesaModel?>(null);
|
||||||
|
|
||||||
|
// Counter untuk notifikasi
|
||||||
|
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||||
|
|
||||||
|
// Counter untuk permintaan menunggu
|
||||||
|
final RxInt jumlahMenunggu = 0.obs;
|
||||||
|
|
||||||
|
// Counter untuk pengaduan yang diproses
|
||||||
|
final RxInt jumlahDiproses = 0.obs;
|
||||||
|
|
||||||
|
// Data jadwal hari ini
|
||||||
|
final RxList<dynamic> jadwalHariIni = <dynamic>[].obs;
|
||||||
|
|
||||||
UserModel? get user => _authController.user;
|
UserModel? get user => _authController.user;
|
||||||
String get role => user?.role ?? 'PETUGASDESA';
|
String get role => user?.role ?? 'PETUGASDESA';
|
||||||
String get nama => user?.name ?? 'Petugas Desa';
|
String get nama => user?.name ?? 'Petugas Desa';
|
||||||
|
|
||||||
|
// Getter untuk nama lengkap dari profil pengguna
|
||||||
|
String get namaLengkap => userProfile['name'] ?? user?.name ?? 'Petugas Desa';
|
||||||
|
|
||||||
|
// Getter untuk nama desa dari profil pengguna
|
||||||
|
String get desa {
|
||||||
|
// Prioritaskan model desa dari user
|
||||||
|
if (user?.desa != null) {
|
||||||
|
print('DEBUG: Menggunakan desa dari user model: ${user!.desa!.nama}');
|
||||||
|
return user!.desa!.nama;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kemudian coba dari userProfile
|
||||||
|
if (userProfile['desa'] != null && userProfile['desa'] is Map) {
|
||||||
|
final desaNama = userProfile['desa']['nama'] ?? 'Desa';
|
||||||
|
print('DEBUG: Menggunakan desa dari userProfile: $desaNama');
|
||||||
|
return desaNama;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback ke nilai default
|
||||||
|
print('DEBUG: Menggunakan nilai default untuk desa');
|
||||||
|
return userProfile['desa_id'] != null ? 'Desa' : 'Desa';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
loadUserProfile();
|
||||||
// Inisialisasi manual untuk pengaduan (untuk debugging)
|
|
||||||
jumlahDiproses.value = 3;
|
|
||||||
print('onInit - Jumlah pengaduan diproses: ${jumlahDiproses.value}');
|
|
||||||
|
|
||||||
loadRoleData();
|
|
||||||
loadDashboardData();
|
|
||||||
loadJadwalData();
|
|
||||||
loadPermintaanPenjadwalanData();
|
|
||||||
loadNotifikasiData();
|
loadNotifikasiData();
|
||||||
loadInventarisData();
|
loadJadwalData();
|
||||||
loadPenitipanData();
|
loadPenitipanData();
|
||||||
loadPengaduanData();
|
loadPengaduanData();
|
||||||
}
|
}
|
||||||
@ -93,584 +76,106 @@ class PetugasDesaController extends GetxController {
|
|||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadRoleData() async {
|
// Metode untuk memuat data profil pengguna dari cache
|
||||||
isLoading.value = true;
|
Future<void> loadUserProfile() async {
|
||||||
try {
|
try {
|
||||||
|
// Jika user sudah ada di AuthController, tidak perlu mengambil data lagi
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
final data = await _supabaseService.getRoleSpecificData(role);
|
print('DEBUG: User ditemukan di AuthController: ${user!.email}');
|
||||||
roleData.value = data;
|
print('DEBUG: User desa: ${user!.desa?.nama}');
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error loading role data: $e');
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadDashboardData() async {
|
// Ambil data tambahan jika diperlukan, tapi gunakan cache
|
||||||
|
final profileData = await _supabaseService.getUserProfile();
|
||||||
|
if (profileData != null) {
|
||||||
|
print('DEBUG: Profile data ditemukan: ${profileData['name']}');
|
||||||
|
userProfile.value = profileData;
|
||||||
|
|
||||||
|
// Parse data desa jika ada
|
||||||
|
if (profileData['desa'] != null &&
|
||||||
|
profileData['desa'] is Map<String, dynamic>) {
|
||||||
try {
|
try {
|
||||||
// Simulasi data untuk dashboard
|
final desaData = profileData['desa'] as Map<String, dynamic>;
|
||||||
await Future.delayed(const Duration(milliseconds: 800));
|
print('DEBUG: Desa data ditemukan: $desaData');
|
||||||
|
|
||||||
totalPenerima.value = 120;
|
|
||||||
totalBantuan.value = 5;
|
|
||||||
totalPenyaluran.value = 8;
|
|
||||||
progressPenyaluran.value = 0.75;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getDashboardData();
|
|
||||||
// totalPenerima.value = result['total_penerima'] ?? 0;
|
|
||||||
// totalBantuan.value = result['total_bantuan'] ?? 0;
|
|
||||||
// totalPenyaluran.value = result['total_penyaluran'] ?? 0;
|
|
||||||
// progressPenyaluran.value = result['progress_penyaluran'] ?? 0.0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading dashboard data: $e');
|
print('Error parsing desa data: $e');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
print('DEBUG: Desa data tidak ditemukan atau bukan Map');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('DEBUG: Profile data tidak ditemukan');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('DEBUG: User tidak ditemukan di AuthController');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadJadwalData() async {
|
|
||||||
try {
|
|
||||||
// Simulasi data untuk jadwal
|
|
||||||
await Future.delayed(const Duration(milliseconds: 600));
|
|
||||||
|
|
||||||
jadwalHariIni.value = [
|
|
||||||
{
|
|
||||||
'id': '1',
|
|
||||||
'lokasi': 'Balai Desa Sukamaju',
|
|
||||||
'jenis_bantuan': 'Beras',
|
|
||||||
'tanggal': '15 April 2023',
|
|
||||||
'waktu': '09:00 - 12:00',
|
|
||||||
'status': 'aktif',
|
|
||||||
'jumlah_penerima': 45,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'lokasi': 'Pos RW 03',
|
|
||||||
'jenis_bantuan': 'Paket Sembako',
|
|
||||||
'tanggal': '15 April 2023',
|
|
||||||
'waktu': '13:00 - 15:00',
|
|
||||||
'status': 'aktif',
|
|
||||||
'jumlah_penerima': 30,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
jadwalMendatang.value = [
|
|
||||||
{
|
|
||||||
'id': '3',
|
|
||||||
'lokasi': 'Balai Desa Sukamaju',
|
|
||||||
'jenis_bantuan': 'Beras',
|
|
||||||
'tanggal': '22 April 2023',
|
|
||||||
'waktu': '09:00 - 12:00',
|
|
||||||
'status': 'terjadwal',
|
|
||||||
'jumlah_penerima': 50,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '4',
|
|
||||||
'lokasi': 'Pos RW 05',
|
|
||||||
'jenis_bantuan': 'Paket Sembako',
|
|
||||||
'tanggal': '23 April 2023',
|
|
||||||
'waktu': '13:00 - 15:00',
|
|
||||||
'status': 'terjadwal',
|
|
||||||
'jumlah_penerima': 35,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
jadwalSelesai.value = [
|
|
||||||
{
|
|
||||||
'id': '5',
|
|
||||||
'lokasi': 'Balai Desa Sukamaju',
|
|
||||||
'jenis_bantuan': 'Beras',
|
|
||||||
'tanggal': '8 April 2023',
|
|
||||||
'waktu': '09:00 - 12:00',
|
|
||||||
'status': 'selesai',
|
|
||||||
'jumlah_penerima': 48,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '6',
|
|
||||||
'lokasi': 'Pos RW 02',
|
|
||||||
'jenis_bantuan': 'Paket Sembako',
|
|
||||||
'tanggal': '9 April 2023',
|
|
||||||
'waktu': '13:00 - 15:00',
|
|
||||||
'status': 'selesai',
|
|
||||||
'jumlah_penerima': 32,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getJadwalData();
|
|
||||||
// jadwalHariIni.value = result['hari_ini'] ?? [];
|
|
||||||
// jadwalMendatang.value = result['mendatang'] ?? [];
|
|
||||||
// jadwalSelesai.value = result['selesai'] ?? [];
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading jadwal data: $e');
|
print('Error loading user profile: $e');
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadPermintaanPenjadwalanData() async {
|
|
||||||
try {
|
|
||||||
// Simulasi data untuk permintaan penjadwalan
|
|
||||||
await Future.delayed(const Duration(milliseconds: 600));
|
|
||||||
|
|
||||||
permintaanPenjadwalan.value = [
|
|
||||||
{
|
|
||||||
'id': '1',
|
|
||||||
'nama': 'Ahmad Sulaiman',
|
|
||||||
'nik': '3201234567890001',
|
|
||||||
'jenis_bantuan': 'Beras',
|
|
||||||
'tanggal_permintaan': '14 April 2023',
|
|
||||||
'alamat': 'Dusun Sukamaju RT 02/03',
|
|
||||||
'status': 'menunggu',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'nama': 'Siti Aminah',
|
|
||||||
'nik': '3201234567890002',
|
|
||||||
'jenis_bantuan': 'Sembako',
|
|
||||||
'tanggal_permintaan': '13 April 2023',
|
|
||||||
'alamat': 'Dusun Sukamaju RT 01/03',
|
|
||||||
'status': 'menunggu',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getPermintaanPenjadwalanData();
|
|
||||||
// permintaanPenjadwalan.value = result ?? [];
|
|
||||||
// jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
|
||||||
} catch (e) {
|
|
||||||
print('Error loading permintaan penjadwalan data: $e');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat data notifikasi
|
||||||
Future<void> loadNotifikasiData() async {
|
Future<void> loadNotifikasiData() async {
|
||||||
try {
|
try {
|
||||||
// Simulasi data untuk notifikasi
|
if (user != null) {
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
final notifikasiData =
|
||||||
|
await _supabaseService.getNotifikasiBelumDibaca(user!.id);
|
||||||
// Hitung jumlah notifikasi yang belum dibaca
|
if (notifikasiData != null) {
|
||||||
final List<Map<String, dynamic>> notifikasi = [
|
jumlahNotifikasiBelumDibaca.value = notifikasiData.length;
|
||||||
{
|
}
|
||||||
'id': '1',
|
}
|
||||||
'judul': 'Jadwal Penyaluran Baru',
|
|
||||||
'pesan': 'Jadwal penyaluran beras telah ditambahkan untuk hari ini',
|
|
||||||
'waktu': '08:30',
|
|
||||||
'dibaca': false,
|
|
||||||
'tanggal': 'hari_ini',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'judul': 'Pengajuan Bantuan Baru',
|
|
||||||
'pesan': 'Ada 3 pengajuan bantuan baru yang perlu diverifikasi',
|
|
||||||
'waktu': '10:15',
|
|
||||||
'dibaca': false,
|
|
||||||
'tanggal': 'hari_ini',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '3',
|
|
||||||
'judul': 'Laporan Penyaluran',
|
|
||||||
'pesan':
|
|
||||||
'Laporan penyaluran bantuan tanggal 14 April 2023 telah selesai',
|
|
||||||
'waktu': '16:45',
|
|
||||||
'dibaca': true,
|
|
||||||
'tanggal': 'kemarin',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
notifikasiBelumDibaca.value =
|
|
||||||
notifikasi.where((n) => n['dibaca'] == false).toList();
|
|
||||||
jumlahNotifikasiBelumDibaca.value = notifikasiBelumDibaca.length;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getNotifikasiData();
|
|
||||||
// notifikasiBelumDibaca.value = result.where((n) => n['dibaca'] == false).toList();
|
|
||||||
// jumlahNotifikasiBelumDibaca.value = notifikasiBelumDibaca.length;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading notifikasi data: $e');
|
print('Error loading notifikasi data: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadInventarisData() async {
|
// Metode untuk memuat data jadwal
|
||||||
|
Future<void> loadJadwalData() async {
|
||||||
try {
|
try {
|
||||||
// Simulasi data untuk inventaris
|
final jadwalHariIniData = await _supabaseService.getJadwalHariIni();
|
||||||
await Future.delayed(const Duration(milliseconds: 700));
|
if (jadwalHariIniData != null) {
|
||||||
|
jadwalHariIni.value = jadwalHariIniData;
|
||||||
daftarInventaris.value = [
|
}
|
||||||
{
|
|
||||||
'id': '1',
|
|
||||||
'nama': 'Beras',
|
|
||||||
'jenis': 'Sembako',
|
|
||||||
'stok': '750 kg',
|
|
||||||
'stok_angka': 750.0,
|
|
||||||
'lokasi': 'Gudang Utama',
|
|
||||||
'tanggal_masuk': '10 April 2023',
|
|
||||||
'kadaluarsa': '10 April 2024',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'nama': 'Minyak Goreng',
|
|
||||||
'jenis': 'Sembako',
|
|
||||||
'stok': '250 liter',
|
|
||||||
'stok_angka': 250.0,
|
|
||||||
'lokasi': 'Gudang Utama',
|
|
||||||
'tanggal_masuk': '12 April 2023',
|
|
||||||
'kadaluarsa': '12 Oktober 2023',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '3',
|
|
||||||
'nama': 'Paket Sembako',
|
|
||||||
'jenis': 'Paket Bantuan',
|
|
||||||
'stok': '100 paket',
|
|
||||||
'stok_angka': 100.0,
|
|
||||||
'lokasi': 'Gudang Cabang',
|
|
||||||
'tanggal_masuk': '15 April 2023',
|
|
||||||
'kadaluarsa': '15 Juli 2023',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Hitung total stok, stok masuk, dan stok keluar
|
|
||||||
totalStok.value = daftarInventaris.fold(
|
|
||||||
0, (sum, item) => sum + (item['stok_angka'] as double));
|
|
||||||
stokMasuk.value = 500.0; // Contoh data
|
|
||||||
stokKeluar.value = 350.0; // Contoh data
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getInventarisData();
|
|
||||||
// daftarInventaris.value = result['daftar'] ?? [];
|
|
||||||
// totalStok.value = result['total_stok'] ?? 0.0;
|
|
||||||
// stokMasuk.value = result['stok_masuk'] ?? 0.0;
|
|
||||||
// stokKeluar.value = result['stok_keluar'] ?? 0.0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading inventaris data: $e');
|
print('Error loading jadwal data: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat data penitipan
|
||||||
Future<void> loadPenitipanData() async {
|
Future<void> loadPenitipanData() async {
|
||||||
try {
|
try {
|
||||||
// Simulasi data untuk penitipan
|
// Simulasi data untuk contoh
|
||||||
await Future.delayed(const Duration(milliseconds: 600));
|
jumlahMenunggu.value = 3;
|
||||||
|
|
||||||
daftarPenitipan.value = [
|
|
||||||
{
|
|
||||||
'id': '1',
|
|
||||||
'donatur': 'PT Sejahtera Abadi',
|
|
||||||
'jenis_bantuan': 'Sembako',
|
|
||||||
'jumlah': '500 kg',
|
|
||||||
'tanggal_pengajuan': '15 April 2023',
|
|
||||||
'status': 'Menunggu',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'donatur': 'Yayasan Peduli Sesama',
|
|
||||||
'jenis_bantuan': 'Pakaian',
|
|
||||||
'jumlah': '200 pcs',
|
|
||||||
'tanggal_pengajuan': '14 April 2023',
|
|
||||||
'status': 'Terverifikasi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '3',
|
|
||||||
'donatur': 'Bank BRI',
|
|
||||||
'jenis_bantuan': 'Beras',
|
|
||||||
'jumlah': '300 kg',
|
|
||||||
'tanggal_pengajuan': '13 April 2023',
|
|
||||||
'status': 'Terverifikasi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '4',
|
|
||||||
'donatur': 'Komunitas Peduli',
|
|
||||||
'jenis_bantuan': 'Alat Tulis',
|
|
||||||
'jumlah': '100 set',
|
|
||||||
'tanggal_pengajuan': '12 April 2023',
|
|
||||||
'status': 'Ditolak',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Hitung jumlah penitipan berdasarkan status
|
|
||||||
jumlahMenunggu.value =
|
|
||||||
daftarPenitipan.where((p) => p['status'] == 'Menunggu').length;
|
|
||||||
jumlahTerverifikasi.value =
|
|
||||||
daftarPenitipan.where((p) => p['status'] == 'Terverifikasi').length;
|
|
||||||
jumlahDitolak.value =
|
|
||||||
daftarPenitipan.where((p) => p['status'] == 'Ditolak').length;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getPenitipanData();
|
|
||||||
// daftarPenitipan.value = result ?? [];
|
|
||||||
// jumlahMenunggu.value = daftarPenitipan.where((p) => p['status'] == 'Menunggu').length;
|
|
||||||
// jumlahTerverifikasi.value = daftarPenitipan.where((p) => p['status'] == 'Terverifikasi').length;
|
|
||||||
// jumlahDitolak.value = daftarPenitipan.where((p) => p['status'] == 'Ditolak').length;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading penitipan data: $e');
|
print('Error loading penitipan data: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat data pengaduan
|
||||||
Future<void> loadPengaduanData() async {
|
Future<void> loadPengaduanData() async {
|
||||||
try {
|
try {
|
||||||
// Simulasi data untuk pengaduan
|
// Simulasi data untuk contoh
|
||||||
await Future.delayed(const Duration(milliseconds: 650));
|
jumlahDiproses.value = 2;
|
||||||
|
|
||||||
// Pastikan data pengaduan tidak kosong
|
|
||||||
daftarPengaduan.value = [
|
|
||||||
{
|
|
||||||
'id': '1',
|
|
||||||
'nama': 'Budi Santoso',
|
|
||||||
'nik': '3201020107030011',
|
|
||||||
'jenis_pengaduan': 'Bantuan Tidak Diterima',
|
|
||||||
'deskripsi':
|
|
||||||
'Saya belum menerima bantuan beras yang dijadwalkan minggu lalu',
|
|
||||||
'tanggal': '15 April 2023',
|
|
||||||
'status': 'Diproses',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '2',
|
|
||||||
'nama': 'Siti Rahayu',
|
|
||||||
'nik': '3201020107030010',
|
|
||||||
'jenis_pengaduan': 'Kualitas Bantuan',
|
|
||||||
'deskripsi':
|
|
||||||
'Beras yang diterima berkualitas buruk dan tidak layak konsumsi',
|
|
||||||
'tanggal': '14 April 2023',
|
|
||||||
'status': 'Tindakan',
|
|
||||||
'tindakan':
|
|
||||||
'Pengecekan kualitas beras di gudang dan pengambilan sampel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '3',
|
|
||||||
'nama': 'Ahmad Fauzi',
|
|
||||||
'nik': '3201020107030013',
|
|
||||||
'jenis_pengaduan': 'Jumlah Bantuan',
|
|
||||||
'deskripsi':
|
|
||||||
'Jumlah bantuan yang diterima tidak sesuai dengan yang dijanjikan',
|
|
||||||
'tanggal': '13 April 2023',
|
|
||||||
'status': 'Tindakan',
|
|
||||||
'tindakan':
|
|
||||||
'Verifikasi data penerima dan jumlah bantuan yang seharusnya diterima',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '4',
|
|
||||||
'nama': 'Dewi Lestari',
|
|
||||||
'nik': '3201020107030012',
|
|
||||||
'jenis_pengaduan': 'Jadwal Penyaluran',
|
|
||||||
'deskripsi':
|
|
||||||
'Jadwal penyaluran bantuan sering berubah tanpa pemberitahuan',
|
|
||||||
'tanggal': '10 April 2023',
|
|
||||||
'status': 'Selesai',
|
|
||||||
'tindakan':
|
|
||||||
'Koordinasi dengan tim penyaluran untuk perbaikan sistem pemberitahuan',
|
|
||||||
'hasil':
|
|
||||||
'Implementasi sistem notifikasi perubahan jadwal melalui SMS dan pengumuman di balai desa',
|
|
||||||
},
|
|
||||||
// Tambahkan data pengaduan dengan status 'Diproses' untuk memastikan counter muncul
|
|
||||||
{
|
|
||||||
'id': '5',
|
|
||||||
'nama': 'Joko Widodo',
|
|
||||||
'nik': '3201020107030014',
|
|
||||||
'jenis_pengaduan': 'Bantuan Tidak Sesuai',
|
|
||||||
'deskripsi':
|
|
||||||
'Bantuan yang diterima tidak sesuai dengan yang dijanjikan',
|
|
||||||
'tanggal': '16 April 2023',
|
|
||||||
'status': 'Diproses',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': '6',
|
|
||||||
'nama': 'Anita Sari',
|
|
||||||
'nik': '3201020107030015',
|
|
||||||
'jenis_pengaduan': 'Bantuan Tidak Tepat Sasaran',
|
|
||||||
'deskripsi':
|
|
||||||
'Bantuan diberikan kepada warga yang tidak berhak menerima',
|
|
||||||
'tanggal': '17 April 2023',
|
|
||||||
'status': 'Diproses',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Hitung jumlah pengaduan berdasarkan status
|
|
||||||
int jumlahDiprosesTemp =
|
|
||||||
daftarPengaduan.where((p) => p['status'] == 'Diproses').length;
|
|
||||||
int jumlahTindakanTemp =
|
|
||||||
daftarPengaduan.where((p) => p['status'] == 'Tindakan').length;
|
|
||||||
int jumlahSelesaiTemp =
|
|
||||||
daftarPengaduan.where((p) => p['status'] == 'Selesai').length;
|
|
||||||
|
|
||||||
// Update nilai Rx
|
|
||||||
jumlahDiproses.value = jumlahDiprosesTemp;
|
|
||||||
jumlahTindakan.value = jumlahTindakanTemp;
|
|
||||||
jumlahSelesai.value = jumlahSelesaiTemp;
|
|
||||||
|
|
||||||
// Print untuk debugging
|
|
||||||
print('Data pengaduan dimuat:');
|
|
||||||
print('Jumlah pengaduan diproses: ${jumlahDiproses.value}');
|
|
||||||
print('Jumlah pengaduan tindakan: ${jumlahTindakan.value}');
|
|
||||||
print('Jumlah pengaduan selesai: ${jumlahSelesai.value}');
|
|
||||||
print('Total pengaduan: ${daftarPengaduan.length}');
|
|
||||||
|
|
||||||
// Perbarui UI secara manual
|
|
||||||
update();
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diambil dari Supabase
|
|
||||||
// final result = await _supabaseService.getPengaduanData();
|
|
||||||
// daftarPengaduan.value = result ?? [];
|
|
||||||
// jumlahDiproses.value = daftarPengaduan.where((p) => p['status'] == 'Diproses').length;
|
|
||||||
// jumlahTindakan.value = daftarPengaduan.where((p) => p['status'] == 'Tindakan').length;
|
|
||||||
// jumlahSelesai.value = daftarPengaduan.where((p) => p['status'] == 'Selesai').length;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error loading pengaduan data: $e');
|
print('Error loading pengaduan data: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method untuk memperbarui jumlah pengaduan secara manual (untuk debugging)
|
// Metode untuk memperbarui counter pengaduan
|
||||||
void updatePengaduanCounter() {
|
Future<void> updatePengaduanCounter() async {
|
||||||
jumlahDiproses.value = 5; // Set nilai secara manual
|
try {
|
||||||
update(); // Perbarui UI
|
await loadPengaduanData();
|
||||||
print(
|
} catch (e) {
|
||||||
'Counter pengaduan diperbarui secara manual: ${jumlahDiproses.value}');
|
print('Error updating pengaduan counter: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
void tandaiNotifikasiDibaca(String id) {
|
|
||||||
// Implementasi untuk menandai notifikasi sebagai dibaca
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status notifikasi
|
|
||||||
// await _supabaseService.markNotificationAsRead(id);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadNotifikasiData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tambahInventaris(Map<String, dynamic> data) {
|
|
||||||
// Implementasi untuk menambah inventaris
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk menambah data inventaris
|
|
||||||
// await _supabaseService.addInventory(data);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadInventarisData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void hapusInventaris(String id) {
|
|
||||||
// Implementasi untuk menghapus inventaris
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk menghapus data inventaris
|
|
||||||
// await _supabaseService.deleteInventory(id);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadInventarisData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void terimaPermohonanPenitipan(String id) {
|
|
||||||
// Implementasi untuk menerima permohonan penitipan
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status penitipan
|
|
||||||
// await _supabaseService.acceptDeposit(id);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadPenitipanData();
|
|
||||||
loadInventarisData(); // Perbarui inventaris karena ada penambahan stok
|
|
||||||
}
|
|
||||||
|
|
||||||
void tolakPermohonanPenitipan(String id) {
|
|
||||||
// Implementasi untuk menolak permohonan penitipan
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status penitipan
|
|
||||||
// await _supabaseService.rejectDeposit(id);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadPenitipanData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void prosesPengaduan(String id, String tindakan) {
|
|
||||||
// Implementasi untuk memproses pengaduan
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan
|
|
||||||
// await _supabaseService.processPengaduan(id, tindakan);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadPengaduanData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void selesaikanPengaduan(String id, String hasil) {
|
|
||||||
// Implementasi untuk menyelesaikan pengaduan
|
|
||||||
// Di implementasi nyata, akan memanggil Supabase untuk memperbarui status pengaduan
|
|
||||||
// await _supabaseService.completePengaduan(id, hasil);
|
|
||||||
|
|
||||||
// Perbarui data lokal
|
|
||||||
loadPengaduanData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void logout() {
|
|
||||||
_authController.logout();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metode untuk mengubah tab aktif
|
||||||
void changeTab(int index) {
|
void changeTab(int index) {
|
||||||
activeTabIndex.value = index;
|
activeTabIndex.value = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk konfirmasi permintaan penjadwalan
|
// Metode untuk logout
|
||||||
Future<void> konfirmasiPermintaanPenjadwalan(
|
Future<void> logout() async {
|
||||||
String id, String jadwalId) async {
|
await _authController.logout();
|
||||||
try {
|
|
||||||
if (id.isEmpty || jadwalId.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Error',
|
|
||||||
'ID permintaan atau jadwal tidak valid',
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true;
|
|
||||||
|
|
||||||
// Simulasi proses konfirmasi
|
|
||||||
await Future.delayed(const Duration(milliseconds: 800));
|
|
||||||
|
|
||||||
// Hapus permintaan dari daftar
|
|
||||||
permintaanPenjadwalan.removeWhere((item) => item['id'] == id);
|
|
||||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diupdate ke Supabase
|
|
||||||
// await _supabaseService.konfirmasiPermintaanPenjadwalan(id, jadwalId);
|
|
||||||
// await loadPermintaanPenjadwalanData();
|
|
||||||
// await loadJadwalData();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error konfirmasi permintaan penjadwalan: $e');
|
|
||||||
Get.snackbar(
|
|
||||||
'Error',
|
|
||||||
'Terjadi kesalahan saat mengkonfirmasi permintaan',
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk menolak permintaan penjadwalan
|
|
||||||
Future<void> tolakPermintaanPenjadwalan(String id, String alasan) async {
|
|
||||||
try {
|
|
||||||
if (id.isEmpty) {
|
|
||||||
Get.snackbar(
|
|
||||||
'Error',
|
|
||||||
'ID permintaan tidak valid',
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading.value = true;
|
|
||||||
|
|
||||||
// Simulasi proses penolakan
|
|
||||||
await Future.delayed(const Duration(milliseconds: 800));
|
|
||||||
|
|
||||||
// Hapus permintaan dari daftar
|
|
||||||
permintaanPenjadwalan.removeWhere((item) => item['id'] == id);
|
|
||||||
jumlahPermintaanPenjadwalan.value = permintaanPenjadwalan.length;
|
|
||||||
|
|
||||||
// Di implementasi nyata, data akan diupdate ke Supabase
|
|
||||||
// await _supabaseService.tolakPermintaanPenjadwalan(id, alasan);
|
|
||||||
// await loadPermintaanPenjadwalanData();
|
|
||||||
} catch (e) {
|
|
||||||
print('Error tolak permintaan penjadwalan: $e');
|
|
||||||
Get.snackbar(
|
|
||||||
'Error',
|
|
||||||
'Terjadi kesalahan saat menolak permintaan',
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
colorText: Colors.white,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/notifikasi_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class PetugasDesaDashboardController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Data profil pengguna dari cache
|
||||||
|
final RxMap<String, dynamic> userProfile = RxMap<String, dynamic>({});
|
||||||
|
|
||||||
|
// Data untuk dashboard
|
||||||
|
final RxInt totalPenerima = 0.obs;
|
||||||
|
final RxInt totalBantuan = 0.obs;
|
||||||
|
final RxInt totalPenyaluran = 0.obs;
|
||||||
|
final RxDouble progressPenyaluran = 0.0.obs;
|
||||||
|
|
||||||
|
// Data untuk notifikasi
|
||||||
|
final RxList<NotifikasiModel> notifikasiBelumDibaca = <NotifikasiModel>[].obs;
|
||||||
|
final RxInt jumlahNotifikasiBelumDibaca = 0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
String get role => user?.role ?? 'PETUGASDESA';
|
||||||
|
String get nama => user?.name ?? 'Petugas Desa';
|
||||||
|
|
||||||
|
// Getter untuk nama lengkap dari profil pengguna
|
||||||
|
String get namaLengkap => userProfile['name'] ?? user?.name ?? 'Petugas Desa';
|
||||||
|
|
||||||
|
// Getter untuk nama desa dari profil pengguna
|
||||||
|
String get desa =>
|
||||||
|
userProfile['desa']?['nama'] ??
|
||||||
|
(userProfile['desa_id'] != null ? 'Desa' : 'Desa');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadUserProfile();
|
||||||
|
loadDashboardData();
|
||||||
|
loadNotifikasiData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk memuat data profil pengguna dari cache
|
||||||
|
Future<void> loadUserProfile() async {
|
||||||
|
try {
|
||||||
|
// Jika user sudah ada di AuthController, tidak perlu mengambil data lagi
|
||||||
|
if (user != null) {
|
||||||
|
// Ambil data tambahan jika diperlukan, tapi gunakan cache
|
||||||
|
final profileData = await _supabaseService.getUserProfile();
|
||||||
|
if (profileData != null) {
|
||||||
|
userProfile.value = profileData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading user profile: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadDashboardData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Mengambil data total penerima
|
||||||
|
final penerimaData = await _supabaseService.getTotalPenerima();
|
||||||
|
totalPenerima.value = penerimaData ?? 0;
|
||||||
|
|
||||||
|
// Mengambil data total bantuan
|
||||||
|
final bantuanData = await _supabaseService.getTotalBantuan();
|
||||||
|
totalBantuan.value = bantuanData ?? 0;
|
||||||
|
|
||||||
|
// Mengambil data total penyaluran
|
||||||
|
final penyaluranData = await _supabaseService.getTotalPenyaluran();
|
||||||
|
totalPenyaluran.value = penyaluranData ?? 0;
|
||||||
|
|
||||||
|
// Menghitung progress penyaluran
|
||||||
|
if (totalBantuan.value > 0) {
|
||||||
|
progressPenyaluran.value =
|
||||||
|
(totalPenyaluran.value / totalBantuan.value) * 100;
|
||||||
|
} else {
|
||||||
|
progressPenyaluran.value = 0.0;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading dashboard data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadNotifikasiData() async {
|
||||||
|
try {
|
||||||
|
final notifikasiData =
|
||||||
|
await _supabaseService.getNotifikasiBelumDibaca(user?.id ?? '');
|
||||||
|
|
||||||
|
if (notifikasiData != null) {
|
||||||
|
notifikasiBelumDibaca.value = notifikasiData
|
||||||
|
.map((data) => NotifikasiModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
jumlahNotifikasiBelumDibaca.value = notifikasiBelumDibaca.length;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading notifikasi data: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadDashboardData();
|
||||||
|
await loadNotifikasiData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/stok_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
|
|
||||||
|
class StokBantuanController extends GetxController {
|
||||||
|
final AuthController _authController = Get.find<AuthController>();
|
||||||
|
final SupabaseService _supabaseService = SupabaseService.to;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// Data untuk stok bantuan
|
||||||
|
final RxList<StokBantuanModel> daftarStokBantuan = <StokBantuanModel>[].obs;
|
||||||
|
final RxDouble totalStok = 0.0.obs;
|
||||||
|
final RxDouble stokMasuk = 0.0.obs;
|
||||||
|
final RxDouble stokKeluar = 0.0.obs;
|
||||||
|
|
||||||
|
// Controller untuk pencarian
|
||||||
|
final TextEditingController searchController = TextEditingController();
|
||||||
|
final RxString searchQuery = ''.obs;
|
||||||
|
|
||||||
|
UserModel? get user => _authController.user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadStokBantuanData();
|
||||||
|
|
||||||
|
// Listener untuk pencarian
|
||||||
|
searchController.addListener(() {
|
||||||
|
searchQuery.value = searchController.text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
searchController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadStokBantuanData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
final stokBantuanData = await _supabaseService.getStokBantuan();
|
||||||
|
if (stokBantuanData != null) {
|
||||||
|
daftarStokBantuan.value = stokBantuanData
|
||||||
|
.map((data) => StokBantuanModel.fromJson(data))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Hitung total stok
|
||||||
|
totalStok.value = 0;
|
||||||
|
for (var item in daftarStokBantuan) {
|
||||||
|
totalStok.value += item.jumlah ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil data stok masuk dan keluar
|
||||||
|
final stokData = await _supabaseService.getStokStatistics();
|
||||||
|
if (stokData != null) {
|
||||||
|
stokMasuk.value = stokData['masuk'] ?? 0;
|
||||||
|
stokKeluar.value = stokData['keluar'] ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error loading stok bantuan data: $e');
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addStok(StokBantuanModel stok) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.addStok(stok.toJson());
|
||||||
|
await loadStokBantuanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Stok berhasil ditambahkan',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding stok: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menambahkan stok: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateStok(StokBantuanModel stok) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.updateStok(stok.id ?? '', stok.toJson());
|
||||||
|
await loadStokBantuanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Stok berhasil diperbarui',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating stok: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memperbarui stok: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteStok(String stokId) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await _supabaseService.deleteStok(stokId);
|
||||||
|
await loadStokBantuanData();
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Stok berhasil dihapus',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting stok: $e');
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menghapus stok: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
await loadStokBantuanData();
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<StokBantuanModel> getFilteredStokBantuan() {
|
||||||
|
if (searchQuery.isEmpty) {
|
||||||
|
return daftarStokBantuan;
|
||||||
|
} else {
|
||||||
|
return daftarStokBantuan
|
||||||
|
.where((item) =>
|
||||||
|
(item.nama
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(searchQuery.value.toLowerCase()) ??
|
||||||
|
false) ||
|
||||||
|
(item.satuan
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(searchQuery.value.toLowerCase()) ??
|
||||||
|
false) ||
|
||||||
|
(item.deskripsi
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(searchQuery.value.toLowerCase()) ??
|
||||||
|
false))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,9 +22,9 @@ class DashboardView extends GetView<PetugasDesaController> {
|
|||||||
children: [
|
children: [
|
||||||
// Header dengan greeting
|
// Header dengan greeting
|
||||||
GreetingHeader(
|
GreetingHeader(
|
||||||
name: controller.roleData.value?['namaLengkap'] ?? 'Ahmad',
|
name: controller.namaLengkap,
|
||||||
role: 'Petugas Desa',
|
role: 'Petugas Desa',
|
||||||
desa: controller.roleData.value?['Desa'] ?? 'Jatihurip',
|
desa: controller.desa,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
@ -1,387 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
|
||||||
|
|
||||||
class InventarisView extends GetView<PetugasDesaController> {
|
|
||||||
const InventarisView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Ringkasan inventaris
|
|
||||||
_buildInventarisSummary(context),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Filter dan pencarian
|
|
||||||
_buildFilterSearch(context),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// Daftar inventaris
|
|
||||||
_buildInventarisList(context),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInventarisSummary(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: AppTheme.primaryGradient,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Ringkasan Inventaris',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildSummaryItem(
|
|
||||||
context,
|
|
||||||
icon: Icons.inventory_2_outlined,
|
|
||||||
title: 'Total Stok',
|
|
||||||
value: '1,250 kg',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildSummaryItem(
|
|
||||||
context,
|
|
||||||
icon: Icons.input,
|
|
||||||
title: 'Masuk Bulan Ini',
|
|
||||||
value: '500 kg',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildSummaryItem(
|
|
||||||
context,
|
|
||||||
icon: Icons.output,
|
|
||||||
title: 'Keluar Bulan Ini',
|
|
||||||
value: '350 kg',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSummaryItem(
|
|
||||||
BuildContext context, {
|
|
||||||
required IconData icon,
|
|
||||||
required String title,
|
|
||||||
required String value,
|
|
||||||
}) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white.withOpacity(0.2),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
icon,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFilterSearch(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Cari bantuan...',
|
|
||||||
prefixIcon: const Icon(Icons.search),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
filled: true,
|
|
||||||
fillColor: Colors.grey.shade100,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey.shade100,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
// Tampilkan dialog filter
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.filter_list),
|
|
||||||
tooltip: 'Filter',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInventarisList(BuildContext context) {
|
|
||||||
final List<Map<String, dynamic>> inventarisList = [
|
|
||||||
{
|
|
||||||
'nama': 'Beras',
|
|
||||||
'jenis': 'Sembako',
|
|
||||||
'stok': '750 kg',
|
|
||||||
'lokasi': 'Gudang Utama',
|
|
||||||
'tanggal_masuk': '10 April 2023',
|
|
||||||
'kadaluarsa': '10 April 2024',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'nama': 'Minyak Goreng',
|
|
||||||
'jenis': 'Sembako',
|
|
||||||
'stok': '250 liter',
|
|
||||||
'lokasi': 'Gudang Utama',
|
|
||||||
'tanggal_masuk': '12 April 2023',
|
|
||||||
'kadaluarsa': '12 Oktober 2023',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'nama': 'Paket Sembako',
|
|
||||||
'jenis': 'Paket Bantuan',
|
|
||||||
'stok': '100 paket',
|
|
||||||
'lokasi': 'Gudang Cabang',
|
|
||||||
'tanggal_masuk': '15 April 2023',
|
|
||||||
'kadaluarsa': '15 Juli 2023',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'nama': 'Selimut',
|
|
||||||
'jenis': 'Non-Pangan',
|
|
||||||
'stok': '150 buah',
|
|
||||||
'lokasi': 'Gudang Cabang',
|
|
||||||
'tanggal_masuk': '5 April 2023',
|
|
||||||
'kadaluarsa': '-',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Daftar Inventaris',
|
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Navigasi ke halaman tambah inventaris
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text('Tambah'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: AppTheme.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
...inventarisList.map((item) => _buildInventarisItem(context, item)),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildInventarisItem(BuildContext context, Map<String, dynamic> item) {
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withAlpha(26),
|
|
||||||
spreadRadius: 1,
|
|
||||||
blurRadius: 3,
|
|
||||||
offset: const Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
item['nama'] ?? '',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.primaryColor.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
item['jenis'] ?? '',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: AppTheme.primaryColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.inventory,
|
|
||||||
label: 'Stok',
|
|
||||||
value: item['stok'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.location_on_outlined,
|
|
||||||
label: 'Lokasi',
|
|
||||||
value: item['lokasi'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.calendar_today,
|
|
||||||
label: 'Tanggal Masuk',
|
|
||||||
value: item['tanggal_masuk'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildItemDetail(
|
|
||||||
context,
|
|
||||||
icon: Icons.timelapse,
|
|
||||||
label: 'Kadaluarsa',
|
|
||||||
value: item['kadaluarsa'] ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Tampilkan detail inventaris
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.edit_outlined, size: 18),
|
|
||||||
label: const Text('Edit'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.blue,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Tampilkan dialog konfirmasi hapus
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete_outline, size: 18),
|
|
||||||
label: const Text('Hapus'),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Colors.red,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildItemDetail(
|
|
||||||
BuildContext context, {
|
|
||||||
required IconData icon,
|
|
||||||
required String label,
|
|
||||||
required String value,
|
|
||||||
}) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
icon,
|
|
||||||
size: 16,
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
||||||
color: Colors.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -118,8 +118,8 @@ class NotifikasiView extends GetView<PetugasDesaController> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: AppTheme.primaryColor,
|
backgroundColor: AppTheme.primaryColor,
|
||||||
child: const Icon(Icons.done_all),
|
|
||||||
tooltip: 'Tandai Semua Dibaca',
|
tooltip: 'Tandai Semua Dibaca',
|
||||||
|
child: const Icon(Icons.done_all),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -237,8 +237,7 @@ class PelaksanaanPenyaluranView extends GetView<PetugasDesaController> {
|
|||||||
|
|
||||||
// Daftar penerima
|
// Daftar penerima
|
||||||
...daftarPenerima
|
...daftarPenerima
|
||||||
.map((penerima) => _buildPenerimaItem(context, penerima))
|
.map((penerima) => _buildPenerimaItem(context, penerima)),
|
||||||
.toList(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
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';
|
||||||
|
|
||||||
class PenyaluranView extends GetView<PetugasDesaController> {
|
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||||
const PenyaluranView({super.key});
|
const PenyaluranView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa_controller.dart';
|
import 'package:penyaluran_app/app/data/models/penyaluran_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/jadwal_penyaluran_controller.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
class PermintaanPenjadwalanView extends GetView<JadwalPenyaluranController> {
|
||||||
const PermintaanPenjadwalanView({Key? key}) : super(key: key);
|
const PermintaanPenjadwalanView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Pastikan controller sudah diinisialisasi
|
// Pastikan controller sudah diinisialisasi
|
||||||
if (!Get.isRegistered<PetugasDesaController>()) {
|
if (!Get.isRegistered<JadwalPenyaluranController>()) {
|
||||||
Get.put(PetugasDesaController());
|
Get.put(JadwalPenyaluranController());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -236,7 +237,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPermintaanItem(BuildContext context, Map<String, dynamic> item) {
|
Widget _buildPermintaanItem(BuildContext context, PenyaluranBantuanModel item) {
|
||||||
Color statusColor = Colors.orange;
|
Color statusColor = Colors.orange;
|
||||||
IconData statusIcon = Icons.pending_actions;
|
IconData statusIcon = Icons.pending_actions;
|
||||||
|
|
||||||
@ -265,7 +266,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
item['nama'] ?? '',
|
item.judul ?? '',
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@ -307,8 +308,8 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
child: _buildItemDetail(
|
child: _buildItemDetail(
|
||||||
context,
|
context,
|
||||||
icon: Icons.person,
|
icon: Icons.person,
|
||||||
label: 'NIK',
|
label: 'ID',
|
||||||
value: item['nik'] ?? '',
|
value: item.id ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -316,7 +317,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
context,
|
context,
|
||||||
icon: Icons.category,
|
icon: Icons.category,
|
||||||
label: 'Jenis Bantuan',
|
label: 'Jenis Bantuan',
|
||||||
value: item['jenis_bantuan'] ?? '',
|
value: item.judul ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -329,15 +330,15 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
context,
|
context,
|
||||||
icon: Icons.calendar_today,
|
icon: Icons.calendar_today,
|
||||||
label: 'Tanggal Permintaan',
|
label: 'Tanggal Permintaan',
|
||||||
value: item['tanggal_permintaan'] ?? '',
|
value: item.createdAt?.toString().substring(0, 10) ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildItemDetail(
|
child: _buildItemDetail(
|
||||||
context,
|
context,
|
||||||
icon: Icons.location_on,
|
icon: Icons.location_on,
|
||||||
label: 'Alamat',
|
label: 'Deskripsi',
|
||||||
value: item['alamat'] ?? '',
|
value: item.deskripsi ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -420,15 +421,15 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dialog untuk konfirmasi permintaan
|
// Dialog untuk konfirmasi permintaan
|
||||||
void _showKonfirmasiDialog(Map<String, dynamic> permintaan) {
|
void _showKonfirmasiDialog(PenyaluranBantuanModel permintaan) {
|
||||||
String? selectedJadwalId;
|
String? selectedJadwalId;
|
||||||
|
|
||||||
// Data jadwal yang tersedia dari controller
|
// Data jadwal yang tersedia dari controller
|
||||||
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
final jadwalOptions = controller.jadwalMendatang.map((jadwal) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: jadwal['id'],
|
value: jadwal.id,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${jadwal['tanggal'] ?? ''} - ${jadwal['lokasi'] ?? ''} (${jadwal['jenis_bantuan'] ?? ''})'),
|
'${jadwal.tanggalPenjadwalan?.toString().substring(0, 10) ?? ''} - ${jadwal.lokasiPenyaluranId ?? ''} (${jadwal.judul ?? ''})'),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@ -448,7 +449,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'),
|
'Anda akan mengkonfirmasi permintaan penjadwalan dari ${permintaan.judul ?? 'Penerima'}.'),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('Pilih jadwal penyaluran:'),
|
const Text('Pilih jadwal penyaluran:'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -474,9 +475,8 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (selectedJadwalId != null) {
|
if (selectedJadwalId != null) {
|
||||||
// Panggil metode konfirmasi di controller
|
// Panggil metode konfirmasi di controller
|
||||||
controller.konfirmasiPermintaanPenjadwalan(
|
controller.approveJadwal(
|
||||||
permintaan['id'] ?? '',
|
permintaan.id ?? '',
|
||||||
selectedJadwalId ?? '',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Get.back();
|
Get.back();
|
||||||
@ -508,7 +508,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dialog untuk menolak permintaan
|
// Dialog untuk menolak permintaan
|
||||||
void _showTolakDialog(Map<String, dynamic> permintaan) {
|
void _showTolakDialog(PenyaluranBantuanModel permintaan) {
|
||||||
final TextEditingController alasanController = TextEditingController();
|
final TextEditingController alasanController = TextEditingController();
|
||||||
|
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
@ -519,7 +519,7 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Anda akan menolak permintaan penjadwalan dari ${permintaan['nama'] ?? 'Penerima'}.'),
|
'Anda akan menolak permintaan penjadwalan dari ${permintaan.judul ?? 'Penerima'}.'),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('Alasan penolakan:'),
|
const Text('Alasan penolakan:'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -542,8 +542,8 @@ class PermintaanPenjadwalanView extends GetView<PetugasDesaController> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (alasanController.text.trim().isNotEmpty) {
|
if (alasanController.text.trim().isNotEmpty) {
|
||||||
// Panggil metode tolak di controller
|
// Panggil metode tolak di controller
|
||||||
controller.tolakPermintaanPenjadwalan(
|
controller.rejectJadwal(
|
||||||
permintaan['id'] ?? '',
|
permintaan.id ?? '',
|
||||||
alasanController.text.trim(),
|
alasanController.text.trim(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import 'package:penyaluran_app/app/modules/petugas_desa/controllers/petugas_desa
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/dashboard_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/penyaluran_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/penyaluran_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/notifikasi_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/inventaris_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/stok_bantuan_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/penitipan_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/penitipan_view.dart';
|
||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/pengaduan_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/pengaduan_view.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
@ -35,7 +35,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
case 3:
|
case 3:
|
||||||
return const Text('Pengaduan');
|
return const Text('Pengaduan');
|
||||||
case 4:
|
case 4:
|
||||||
return const Text('Inventaris');
|
return const Text('Stok Bantuan');
|
||||||
default:
|
default:
|
||||||
return const Text('Petugas Desa');
|
return const Text('Petugas Desa');
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tombol tambah untuk jadwal dan inventaris
|
// Tombol tambah untuk jadwal dan stok bantuan
|
||||||
if (activeTab == 1) {
|
if (activeTab == 1) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -115,9 +115,9 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
tooltip: 'Tambah Inventaris',
|
tooltip: 'Tambah Stok Bantuan',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Implementasi untuk menambah inventaris baru
|
// Implementasi untuk menambah stok bantuan baru
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
notificationButton,
|
notificationButton,
|
||||||
@ -171,7 +171,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
case 3:
|
case 3:
|
||||||
return const PengaduanView();
|
return const PengaduanView();
|
||||||
case 4:
|
case 4:
|
||||||
return const InventarisView();
|
return const StokBantuanView();
|
||||||
default:
|
default:
|
||||||
return const DashboardView();
|
return const DashboardView();
|
||||||
}
|
}
|
||||||
@ -325,7 +325,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
}),
|
}),
|
||||||
Obx(() => ListTile(
|
Obx(() => ListTile(
|
||||||
leading: const Icon(Icons.inventory_2_outlined),
|
leading: const Icon(Icons.inventory_2_outlined),
|
||||||
title: const Text('Inventaris'),
|
title: const Text('Stok Bantuan'),
|
||||||
selected: controller.activeTabIndex.value == 4,
|
selected: controller.activeTabIndex.value == 4,
|
||||||
selectedColor: AppTheme.primaryColor,
|
selectedColor: AppTheme.primaryColor,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -390,6 +390,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
// Navigasi ke halaman profil
|
// Navigasi ke halaman profil
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
Get.toNamed('/profile');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -623,7 +624,7 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
|||||||
const BottomNavigationBarItem(
|
const BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.inventory_2_outlined),
|
icon: Icon(Icons.inventory_2_outlined),
|
||||||
activeIcon: Icon(Icons.inventory_2),
|
activeIcon: Icon(Icons.inventory_2),
|
||||||
label: 'Inventaris',
|
label: 'Stok Bantuan',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
768
lib/app/modules/petugas_desa/views/stok_bantuan_view.dart
Normal file
768
lib/app/modules/petugas_desa/views/stok_bantuan_view.dart
Normal file
@ -0,0 +1,768 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/stok_bantuan_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/petugas_desa/controllers/stok_bantuan_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
import 'package:penyaluran_app/app/utils/date_formatter.dart';
|
||||||
|
|
||||||
|
class StokBantuanView extends GetView<StokBantuanController> {
|
||||||
|
const StokBantuanView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: controller.refreshData,
|
||||||
|
child: Obx(() => controller.isLoading.value
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _buildContent(context)),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog tambah stok bantuan
|
||||||
|
_showAddStokDialog(context);
|
||||||
|
},
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Ringkasan stok bantuan
|
||||||
|
_buildStokBantuanSummary(context),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Filter dan pencarian
|
||||||
|
_buildFilterSearch(context),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Daftar stok bantuan
|
||||||
|
_buildStokBantuanList(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStokBantuanSummary(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: AppTheme.primaryGradient,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Ringkasan Stok Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.inventory_2_outlined,
|
||||||
|
title: 'Total Stok',
|
||||||
|
value: DateFormatter.formatNumber(controller.totalStok.value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.input,
|
||||||
|
title: 'Masuk',
|
||||||
|
value: DateFormatter.formatNumber(controller.stokMasuk.value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildSummaryItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.output,
|
||||||
|
title: 'Keluar',
|
||||||
|
value:
|
||||||
|
DateFormatter.formatNumber(controller.stokKeluar.value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSummaryItem(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFilterSearch(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller.searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Cari bantuan...',
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade100,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: controller.refreshData,
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
tooltip: 'Refresh',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStokBantuanList(BuildContext context) {
|
||||||
|
return Obx(() {
|
||||||
|
final filteredList = controller.getFilteredStokBantuan();
|
||||||
|
|
||||||
|
if (filteredList.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.inventory_2_outlined,
|
||||||
|
size: 80, color: Colors.grey),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
controller.searchQuery.isEmpty
|
||||||
|
? 'Belum ada data stok bantuan'
|
||||||
|
: 'Tidak ada stok bantuan yang sesuai dengan pencarian',
|
||||||
|
style: const TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Daftar Stok Bantuan',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${filteredList.length} item',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
...filteredList.map((item) => _buildStokBantuanItem(context, item)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStokBantuanItem(BuildContext context, StokBantuanModel item) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withAlpha(26),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
item.nama ?? 'Tanpa Nama',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primaryColor.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
item.status ?? 'TERSEDIA',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: AppTheme.primaryColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (item.deskripsi != null && item.deskripsi!.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4.0),
|
||||||
|
child: Text(
|
||||||
|
item.deskripsi!,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.inventory,
|
||||||
|
label: 'Jumlah',
|
||||||
|
value:
|
||||||
|
'${DateFormatter.formatNumber(item.jumlah)} ${item.satuan ?? ''}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.calendar_today,
|
||||||
|
label: 'Tanggal Masuk',
|
||||||
|
value: DateFormatter.formatDate(item.tanggalMasuk),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.timelapse,
|
||||||
|
label: 'Kadaluarsa',
|
||||||
|
value: DateFormatter.formatDate(item.tanggalKadaluarsa),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildItemDetail(
|
||||||
|
context,
|
||||||
|
icon: Icons.access_time,
|
||||||
|
label: 'Terakhir Diperbarui',
|
||||||
|
value: DateFormatter.formatDate(item.updatedAt),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog edit stok bantuan
|
||||||
|
_showEditStokDialog(context, item);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
||||||
|
label: const Text('Edit'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blue,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// Tampilkan dialog konfirmasi hapus
|
||||||
|
_showDeleteConfirmation(context, item);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete_outline, size: 18),
|
||||||
|
label: const Text('Hapus'),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.red,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItemDetail(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAddStokDialog(BuildContext context) {
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final namaController = TextEditingController();
|
||||||
|
final jumlahController = TextEditingController();
|
||||||
|
final satuanController = TextEditingController();
|
||||||
|
final deskripsiController = TextEditingController();
|
||||||
|
DateTime? tanggalMasuk = DateTime.now();
|
||||||
|
DateTime? tanggalKadaluarsa;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Tambah Stok Bantuan'),
|
||||||
|
content: Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: namaController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nama Bantuan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama bantuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: jumlahController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Jumlah',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Jumlah tidak boleh kosong';
|
||||||
|
}
|
||||||
|
if (double.tryParse(value) == null) {
|
||||||
|
return 'Jumlah harus berupa angka';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: satuanController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Satuan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Satuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: deskripsiController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Deskripsi',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: tanggalMasuk ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(2020),
|
||||||
|
lastDate: DateTime(2030),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
tanggalMasuk = picked;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Tanggal Masuk',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DateFormatter.formatDate(tanggalMasuk),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: tanggalKadaluarsa ??
|
||||||
|
DateTime.now().add(const Duration(days: 365)),
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate: DateTime(2030),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
tanggalKadaluarsa = picked;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Tanggal Kadaluarsa',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DateFormatter.formatDate(tanggalKadaluarsa),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
final stok = StokBantuanModel(
|
||||||
|
nama: namaController.text,
|
||||||
|
jumlah: double.parse(jumlahController.text),
|
||||||
|
satuan: satuanController.text,
|
||||||
|
deskripsi: deskripsiController.text,
|
||||||
|
tanggalMasuk: tanggalMasuk,
|
||||||
|
tanggalKadaluarsa: tanggalKadaluarsa,
|
||||||
|
status: 'TERSEDIA',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
controller.addStok(stok);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Simpan'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEditStokDialog(BuildContext context, StokBantuanModel stok) {
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final namaController = TextEditingController(text: stok.nama);
|
||||||
|
final jumlahController =
|
||||||
|
TextEditingController(text: stok.jumlah?.toString());
|
||||||
|
final satuanController = TextEditingController(text: stok.satuan);
|
||||||
|
final deskripsiController = TextEditingController(text: stok.deskripsi);
|
||||||
|
DateTime? tanggalMasuk = stok.tanggalMasuk;
|
||||||
|
DateTime? tanggalKadaluarsa = stok.tanggalKadaluarsa;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Edit Stok Bantuan'),
|
||||||
|
content: Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: namaController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Nama Bantuan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Nama bantuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: jumlahController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Jumlah',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Jumlah tidak boleh kosong';
|
||||||
|
}
|
||||||
|
if (double.tryParse(value) == null) {
|
||||||
|
return 'Jumlah harus berupa angka';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: satuanController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Satuan',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Satuan tidak boleh kosong';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: deskripsiController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Deskripsi',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: tanggalMasuk ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(2020),
|
||||||
|
lastDate: DateTime(2030),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
tanggalMasuk = picked;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Tanggal Masuk',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DateFormatter.formatDate(tanggalMasuk),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: tanggalKadaluarsa ??
|
||||||
|
DateTime.now().add(const Duration(days: 365)),
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate: DateTime(2030),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
tanggalKadaluarsa = picked;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Tanggal Kadaluarsa',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DateFormatter.formatDate(tanggalKadaluarsa),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (formKey.currentState!.validate()) {
|
||||||
|
final updatedStok = StokBantuanModel(
|
||||||
|
id: stok.id,
|
||||||
|
nama: namaController.text,
|
||||||
|
jumlah: double.parse(jumlahController.text),
|
||||||
|
satuan: satuanController.text,
|
||||||
|
deskripsi: deskripsiController.text,
|
||||||
|
tanggalMasuk: tanggalMasuk,
|
||||||
|
tanggalKadaluarsa: tanggalKadaluarsa,
|
||||||
|
status: stok.status,
|
||||||
|
createdAt: stok.createdAt,
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
controller.updateStok(updatedStok);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Simpan'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDeleteConfirmation(BuildContext context, StokBantuanModel stok) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Konfirmasi Hapus'),
|
||||||
|
content: Text(
|
||||||
|
'Apakah Anda yakin ingin menghapus stok bantuan "${stok.nama}"?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.deleteStok(stok.id ?? '');
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||||
|
child: const Text('Hapus'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
11
lib/app/modules/profile/bindings/profile_binding.dart
Normal file
11
lib/app/modules/profile/bindings/profile_binding.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/profile/controllers/profile_controller.dart';
|
||||||
|
|
||||||
|
class ProfileBinding extends Bindings {
|
||||||
|
@override
|
||||||
|
void dependencies() {
|
||||||
|
Get.lazyPut<ProfileController>(
|
||||||
|
() => ProfileController(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
157
lib/app/modules/profile/controllers/profile_controller.dart
Normal file
157
lib/app/modules/profile/controllers/profile_controller.dart
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/auth_service.dart';
|
||||||
|
|
||||||
|
class ProfileController extends GetxController {
|
||||||
|
final AuthService _authService = Get.find<AuthService>();
|
||||||
|
|
||||||
|
final Rx<User?> user = Rx<User?>(null);
|
||||||
|
final RxBool isLoading = true.obs;
|
||||||
|
final RxBool isEditing = false.obs;
|
||||||
|
|
||||||
|
// Form controllers
|
||||||
|
late TextEditingController nameController;
|
||||||
|
late TextEditingController emailController;
|
||||||
|
late TextEditingController phoneController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
nameController = TextEditingController();
|
||||||
|
emailController = TextEditingController();
|
||||||
|
phoneController = TextEditingController();
|
||||||
|
loadUserData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
nameController.dispose();
|
||||||
|
emailController.dispose();
|
||||||
|
phoneController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadUserData() async {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Mendapatkan data user dari service
|
||||||
|
final userData = await _authService.getCurrentUser();
|
||||||
|
user.value = userData;
|
||||||
|
|
||||||
|
// Mengisi form controllers dengan data user
|
||||||
|
if (userData != null) {
|
||||||
|
nameController.text = userData.name ?? '';
|
||||||
|
emailController.text = userData.email ?? '';
|
||||||
|
phoneController.text = userData.phone ?? '';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memuat data profil: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleEditMode() {
|
||||||
|
isEditing.value = !isEditing.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateProfile() async {
|
||||||
|
if (nameController.text.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Nama tidak boleh kosong',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Update user data
|
||||||
|
final updatedUser = User(
|
||||||
|
id: user.value?.id,
|
||||||
|
name: nameController.text,
|
||||||
|
email: emailController.text,
|
||||||
|
phone: phoneController.text,
|
||||||
|
role: user.value?.role,
|
||||||
|
token: user.value?.token,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Panggil API untuk update profil
|
||||||
|
await _authService.updateProfile(updatedUser);
|
||||||
|
|
||||||
|
// Refresh data
|
||||||
|
await loadUserData();
|
||||||
|
|
||||||
|
// Keluar dari mode edit
|
||||||
|
isEditing.value = false;
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Profil berhasil diperbarui',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memperbarui profil: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> changePassword(String currentPassword, String newPassword,
|
||||||
|
String confirmPassword) async {
|
||||||
|
if (newPassword != confirmPassword) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Konfirmasi password tidak sesuai',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// Panggil API untuk ganti password
|
||||||
|
await _authService.changePassword(currentPassword, newPassword);
|
||||||
|
|
||||||
|
Get.back(); // Tutup dialog
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Password berhasil diubah',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal mengubah password: ${e.toString()}',
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
colorText: Colors.white,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
lib/app/modules/profile/views/profile_view.dart
Normal file
233
lib/app/modules/profile/views/profile_view.dart
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/profile/controllers/profile_controller.dart';
|
||||||
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
|
class ProfileView extends GetView<ProfileController> {
|
||||||
|
const ProfileView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Profil'),
|
||||||
|
actions: [
|
||||||
|
Obx(() {
|
||||||
|
if (controller.isEditing.value) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.save),
|
||||||
|
onPressed: controller.updateProfile,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
onPressed: controller.toggleEditMode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Obx(() {
|
||||||
|
if (controller.isLoading.value) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildProfileHeader(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildProfileForm(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildPasswordSection(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileHeader() {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
child: Icon(
|
||||||
|
Icons.person,
|
||||||
|
size: 60,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Obx(() => Text(
|
||||||
|
controller.user.value?.name ?? 'Pengguna',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Obx(() => Text(
|
||||||
|
controller.user.value?.role?.toUpperCase() ?? 'PENGGUNA',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileForm() {
|
||||||
|
return Obx(() {
|
||||||
|
final isEditing = controller.isEditing.value;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Informasi Pribadi',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Nama
|
||||||
|
TextField(
|
||||||
|
controller: controller.nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Nama Lengkap',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.person),
|
||||||
|
enabled: isEditing,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Email
|
||||||
|
TextField(
|
||||||
|
controller: controller.emailController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Email',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.email),
|
||||||
|
enabled: isEditing,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Nomor Telepon
|
||||||
|
TextField(
|
||||||
|
controller: controller.phoneController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Nomor Telepon',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(Icons.phone),
|
||||||
|
enabled: isEditing,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.phone,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPasswordSection(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Keamanan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () => _showChangePasswordDialog(context),
|
||||||
|
icon: const Icon(Icons.lock),
|
||||||
|
label: const Text('Ubah Password'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
minimumSize: const Size(double.infinity, 50),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showChangePasswordDialog(BuildContext context) {
|
||||||
|
final currentPasswordController = TextEditingController();
|
||||||
|
final newPasswordController = TextEditingController();
|
||||||
|
final confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
|
Get.dialog(
|
||||||
|
AlertDialog(
|
||||||
|
title: const Text('Ubah Password'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: currentPasswordController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Password Saat Ini',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: newPasswordController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Password Baru',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: confirmPasswordController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Konfirmasi Password Baru',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
obscureText: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
controller.changePassword(
|
||||||
|
currentPasswordController.text,
|
||||||
|
newPasswordController.text,
|
||||||
|
confirmPasswordController.text,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppTheme.primaryColor,
|
||||||
|
),
|
||||||
|
child: const Text('Simpan'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import 'package:penyaluran_app/app/routes/app_pages.dart';
|
|||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
|
||||||
class SplashView extends StatefulWidget {
|
class SplashView extends StatefulWidget {
|
||||||
const SplashView({Key? key}) : super(key: key);
|
const SplashView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SplashView> createState() => _SplashViewState();
|
State<SplashView> createState() => _SplashViewState();
|
||||||
|
@ -10,15 +10,24 @@ import 'package:penyaluran_app/app/modules/petugas_desa/views/konfirmasi_penerim
|
|||||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/pelaksanaan_penyaluran_view.dart';
|
import 'package:penyaluran_app/app/modules/petugas_desa/views/pelaksanaan_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/profile/bindings/profile_binding.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/profile/views/profile_view.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/splash/bindings/splash_binding.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/splash/views/splash_view.dart';
|
||||||
|
|
||||||
part 'app_routes.dart';
|
part 'app_routes.dart';
|
||||||
|
|
||||||
class AppPages {
|
class AppPages {
|
||||||
AppPages._();
|
AppPages._();
|
||||||
|
|
||||||
static const initial = Routes.login;
|
static const initial = Routes.splash;
|
||||||
|
|
||||||
static final routes = [
|
static final routes = [
|
||||||
|
GetPage(
|
||||||
|
name: _Paths.splash,
|
||||||
|
page: () => const SplashView(),
|
||||||
|
binding: SplashBinding(),
|
||||||
|
),
|
||||||
GetPage(
|
GetPage(
|
||||||
name: _Paths.login,
|
name: _Paths.login,
|
||||||
page: () => const LoginView(),
|
page: () => const LoginView(),
|
||||||
@ -54,5 +63,10 @@ class AppPages {
|
|||||||
page: () => const PelaksanaanPenyaluranView(),
|
page: () => const PelaksanaanPenyaluranView(),
|
||||||
binding: PetugasDesaBinding(),
|
binding: PetugasDesaBinding(),
|
||||||
),
|
),
|
||||||
|
GetPage(
|
||||||
|
name: _Paths.profile,
|
||||||
|
page: () => const ProfileView(),
|
||||||
|
binding: ProfileBinding(),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ abstract class Routes {
|
|||||||
static const detailPenerima = _Paths.detailPenerima;
|
static const detailPenerima = _Paths.detailPenerima;
|
||||||
static const konfirmasiPenerima = _Paths.konfirmasiPenerima;
|
static const konfirmasiPenerima = _Paths.konfirmasiPenerima;
|
||||||
static const pelaksanaanPenyaluran = _Paths.pelaksanaanPenyaluran;
|
static const pelaksanaanPenyaluran = _Paths.pelaksanaanPenyaluran;
|
||||||
|
static const profile = _Paths.profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Paths {
|
abstract class _Paths {
|
||||||
@ -32,4 +33,5 @@ abstract class _Paths {
|
|||||||
static const detailPenerima = '/daftar-penerima/detail';
|
static const detailPenerima = '/daftar-penerima/detail';
|
||||||
static const konfirmasiPenerima = '/daftar-penerima/konfirmasi';
|
static const konfirmasiPenerima = '/daftar-penerima/konfirmasi';
|
||||||
static const pelaksanaanPenyaluran = '/pelaksanaan-penyaluran';
|
static const pelaksanaanPenyaluran = '/pelaksanaan-penyaluran';
|
||||||
|
static const profile = '/profile';
|
||||||
}
|
}
|
||||||
|
164
lib/app/services/auth_service.dart
Normal file
164
lib/app/services/auth_service.dart
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:penyaluran_app/app/data/models/user_model.dart';
|
||||||
|
|
||||||
|
class AuthService extends GetxService {
|
||||||
|
final Dio _dio = Dio();
|
||||||
|
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||||
|
|
||||||
|
final Rx<User?> currentUser = Rx<User?>(null);
|
||||||
|
|
||||||
|
// Mendapatkan data user saat ini
|
||||||
|
Future<User?> getCurrentUser() async {
|
||||||
|
try {
|
||||||
|
// Implementasi untuk mendapatkan data user dari API atau local storage
|
||||||
|
// Contoh implementasi sederhana:
|
||||||
|
final token = await _storage.read(key: 'token');
|
||||||
|
if (token == null) return null;
|
||||||
|
|
||||||
|
final response = await _dio.get(
|
||||||
|
'/api/user/profile',
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final user = User.fromJson(response.data['data']);
|
||||||
|
currentUser.value = user;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting current user: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update profil user
|
||||||
|
Future<bool> updateProfile(User user) async {
|
||||||
|
try {
|
||||||
|
final token = await _storage.read(key: 'token');
|
||||||
|
if (token == null) return false;
|
||||||
|
|
||||||
|
final response = await _dio.put(
|
||||||
|
'/api/user/profile',
|
||||||
|
data: {
|
||||||
|
'name': user.name,
|
||||||
|
'email': user.email,
|
||||||
|
'phone': user.phone,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
// Update current user
|
||||||
|
currentUser.value = user;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating profile: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ganti password
|
||||||
|
Future<bool> changePassword(
|
||||||
|
String currentPassword, String newPassword) async {
|
||||||
|
try {
|
||||||
|
final token = await _storage.read(key: 'token');
|
||||||
|
if (token == null) return false;
|
||||||
|
|
||||||
|
final response = await _dio.put(
|
||||||
|
'/api/user/change-password',
|
||||||
|
data: {
|
||||||
|
'current_password': currentPassword,
|
||||||
|
'new_password': newPassword,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.statusCode == 200;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error changing password: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login
|
||||||
|
Future<User?> login(String email, String password) async {
|
||||||
|
try {
|
||||||
|
final response = await _dio.post(
|
||||||
|
'/api/auth/login',
|
||||||
|
data: {
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final user = User.fromJson(response.data['data']);
|
||||||
|
|
||||||
|
// Simpan token
|
||||||
|
if (user.token != null) {
|
||||||
|
await _storage.write(key: 'token', value: user.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser.value = user;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error logging in: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
Future<void> logout() async {
|
||||||
|
try {
|
||||||
|
final token = await _storage.read(key: 'token');
|
||||||
|
if (token != null) {
|
||||||
|
await _dio.post(
|
||||||
|
'/api/auth/logout',
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error logging out: $e');
|
||||||
|
} finally {
|
||||||
|
// Hapus token dan user data
|
||||||
|
await _storage.delete(key: 'token');
|
||||||
|
currentUser.value = null;
|
||||||
|
|
||||||
|
// Navigasi ke halaman login
|
||||||
|
Get.offAllNamed('/login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inisialisasi service
|
||||||
|
Future<AuthService> init() async {
|
||||||
|
// Coba mendapatkan user saat ini
|
||||||
|
await getCurrentUser();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,9 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
late final SupabaseClient client;
|
late final SupabaseClient client;
|
||||||
|
|
||||||
|
// Cache untuk profil pengguna
|
||||||
|
Map<String, dynamic>? _cachedUserProfile;
|
||||||
|
|
||||||
// Ganti dengan URL dan API key Supabase Anda
|
// Ganti dengan URL dan API key Supabase Anda
|
||||||
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
|
static const String supabaseUrl = String.fromEnvironment('SUPABASE_URL',
|
||||||
defaultValue: 'http://labulabs.net:8000');
|
defaultValue: 'http://labulabs.net:8000');
|
||||||
@ -42,6 +45,7 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
// Metode untuk logout
|
// Metode untuk logout
|
||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
|
_cachedUserProfile = null; // Hapus cache saat logout
|
||||||
await client.auth.signOut();
|
await client.auth.signOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +57,37 @@ class SupabaseService extends GetxService {
|
|||||||
|
|
||||||
// Metode untuk mendapatkan profil pengguna
|
// Metode untuk mendapatkan profil pengguna
|
||||||
Future<Map<String, dynamic>?> getUserProfile() async {
|
Future<Map<String, dynamic>?> getUserProfile() async {
|
||||||
if (currentUser == null) return null;
|
final user = currentUser;
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Gunakan cache jika tersedia
|
||||||
|
if (_cachedUserProfile != null && _cachedUserProfile!['id'] == user.id) {
|
||||||
|
print('Menggunakan data profil dari cache');
|
||||||
|
return _cachedUserProfile;
|
||||||
|
}
|
||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('user_profile')
|
.from('user_profile')
|
||||||
.select()
|
.select('*, desa:desa_id(id, nama, kecamatan, kabupaten, provinsi)')
|
||||||
.eq('id', currentUser!.id)
|
.eq('id', user.id)
|
||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
print('response: $response');
|
||||||
|
|
||||||
|
// Simpan ke cache
|
||||||
|
_cachedUserProfile = response;
|
||||||
|
|
||||||
|
// Log untuk debugging
|
||||||
|
if (response != null && response['desa'] != null) {
|
||||||
|
print('Desa data: ${response['desa']}');
|
||||||
|
print('Desa type: ${response['desa'].runtimeType}');
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error pada getUserProfile: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan role pengguna
|
// Metode untuk mendapatkan role pengguna
|
||||||
@ -70,64 +96,491 @@ class SupabaseService extends GetxService {
|
|||||||
return profile?['role'];
|
return profile?['role'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan data berdasarkan peran
|
// ==================== PETUGAS DESA METHODS ====================
|
||||||
Future<Map<String, dynamic>?> getRoleSpecificData(String role) async {
|
|
||||||
if (currentUser == null) return null;
|
|
||||||
|
|
||||||
switch (role) {
|
// Dashboard methods
|
||||||
case 'WARGA':
|
Future<int?> getTotalPenerima() async {
|
||||||
return await getWargaByUserId();
|
try {
|
||||||
case 'PETUGASVERIFIKASI':
|
final response =
|
||||||
return await getPetugasVerifikasiData();
|
await client.from('warga').select('id').eq('status', 'AKTIF');
|
||||||
case 'PETUGASDESA':
|
|
||||||
return await getPetugasDesaData();
|
return response.length;
|
||||||
case 'DONATUR':
|
} catch (e) {
|
||||||
return await getDonaturData();
|
print('Error getting total penerima: $e');
|
||||||
default:
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan data petugas verifikasi
|
Future<int?> getTotalBantuan() async {
|
||||||
Future<Map<String, dynamic>?> getPetugasVerifikasiData() async {
|
try {
|
||||||
if (currentUser == null) return null;
|
final response = await client.from('stok_bantuan').select('jumlah');
|
||||||
|
|
||||||
|
double total = 0;
|
||||||
|
for (var item in response) {
|
||||||
|
total += (item['jumlah'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return total.toInt();
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting total bantuan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int?> getTotalPenyaluran() async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('id')
|
||||||
|
.eq('status', 'SELESAI');
|
||||||
|
|
||||||
|
return response.length;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting total penyaluran: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getNotifikasiBelumDibaca(
|
||||||
|
String userId) async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('notifikasi')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.eq('dibaca', false)
|
||||||
|
.order('created_at', ascending: false);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting notifikasi belum dibaca: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jadwal penyaluran methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getJadwalHariIni() async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final today = DateTime(now.year, now.month, now.day);
|
||||||
|
final tomorrow = today.add(const Duration(days: 1));
|
||||||
|
|
||||||
final response = await client
|
final response = await client
|
||||||
.from('xx02_PetugasVerifikasi')
|
.from('penyaluran_bantuan')
|
||||||
.select()
|
.select('*')
|
||||||
.eq('userId', currentUser!.id)
|
.gte('tanggal_penyaluran', today.toIso8601String())
|
||||||
|
.lt('tanggal_penyaluran', tomorrow.toIso8601String())
|
||||||
|
.inFilter('status', ['DISETUJUI', 'BERLANGSUNG']);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting jadwal hari ini: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getJadwalMendatang() async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final tomorrow =
|
||||||
|
DateTime(now.year, now.month, now.day).add(const Duration(days: 1));
|
||||||
|
|
||||||
|
final response = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('*')
|
||||||
|
.gte('tanggal_penyaluran', tomorrow.toIso8601String())
|
||||||
|
.inFilter('status', ['DISETUJUI', 'DIJADWALKAN']);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting jadwal mendatang: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getJadwalSelesai() async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('*')
|
||||||
|
.eq('status', 'SELESAI')
|
||||||
|
.order('tanggal_penyaluran', ascending: false)
|
||||||
|
.limit(10);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting jadwal selesai: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getPermintaanPenjadwalan() async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('penyaluran_bantuan')
|
||||||
|
.select('*')
|
||||||
|
.eq('status', 'MENUNGGU');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting permintaan penjadwalan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> approveJadwal(String jadwalId) async {
|
||||||
|
try {
|
||||||
|
await client.from('penyaluran_bantuan').update({
|
||||||
|
'status': 'DISETUJUI',
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', jadwalId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error approving jadwal: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> rejectJadwal(String jadwalId, String alasan) async {
|
||||||
|
try {
|
||||||
|
await client.from('penyaluran_bantuan').update({
|
||||||
|
'status': 'DITOLAK',
|
||||||
|
'alasan_penolakan': alasan,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', jadwalId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error rejecting jadwal: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completeJadwal(String jadwalId) async {
|
||||||
|
try {
|
||||||
|
await client.from('penyaluran_bantuan').update({
|
||||||
|
'status': 'SELESAI',
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', jadwalId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error completing jadwal: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stok bantuan methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getStokBantuan() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('stok_bantuan').select('*');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting stok bantuan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>?> getStokStatistics() async {
|
||||||
|
try {
|
||||||
|
// Get stok masuk
|
||||||
|
final masukResponse = await client.from('stok_bantuan').select('jumlah');
|
||||||
|
|
||||||
|
double masuk = 0;
|
||||||
|
for (var item in masukResponse) {
|
||||||
|
masuk += (item['jumlah'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get stok keluar
|
||||||
|
final keluarResponse =
|
||||||
|
await client.from('detail_penyaluran').select('jumlah');
|
||||||
|
|
||||||
|
double keluar = 0;
|
||||||
|
for (var item in keluarResponse) {
|
||||||
|
keluar += (item['jumlah'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'masuk': masuk,
|
||||||
|
'keluar': keluar,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting stok statistics: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getBentukBantuan() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('bentuk_bantuan').select('*');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting bentuk bantuan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addStok(Map<String, dynamic> stok) async {
|
||||||
|
try {
|
||||||
|
await client.from('stok_bantuan').insert(stok);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding stok: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateStok(String stokId, Map<String, dynamic> stok) async {
|
||||||
|
try {
|
||||||
|
await client.from('stok_bantuan').update(stok).eq('id', stokId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating stok: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteStok(String stokId) async {
|
||||||
|
try {
|
||||||
|
await client.from('stok_bantuan').delete().eq('id', stokId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting stok: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Penitipan bantuan methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getPenitipanBantuan() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('penitipan_bantuan').select('*');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting penitipan bantuan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> verifikasiPenitipan(String penitipanId) async {
|
||||||
|
try {
|
||||||
|
await client.from('penitipan_bantuan').update({
|
||||||
|
'status': 'TERVERIFIKASI',
|
||||||
|
'tanggal_verifikasi': DateTime.now().toIso8601String(),
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', penitipanId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error verifying penitipan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tolakPenitipan(String penitipanId, String alasan) async {
|
||||||
|
try {
|
||||||
|
await client.from('penitipan_bantuan').update({
|
||||||
|
'status': 'DITOLAK',
|
||||||
|
'alasan_penolakan': alasan,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', penitipanId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error rejecting penitipan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>?> getDonaturById(String donaturId) async {
|
||||||
|
try {
|
||||||
|
final response =
|
||||||
|
await client.from('donatur').select('*').eq('id', donaturId).single();
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting donatur by id: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pengaduan methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getPengaduan() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('pengaduan').select('*');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting pengaduan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> prosesPengaduan(String pengaduanId) async {
|
||||||
|
try {
|
||||||
|
await client.from('pengaduan').update({
|
||||||
|
'status': 'DIPROSES',
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', pengaduanId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error processing pengaduan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tambahTindakanPengaduan(Map<String, dynamic> tindakan) async {
|
||||||
|
try {
|
||||||
|
await client.from('tindakan_pengaduan').insert(tindakan);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding tindakan pengaduan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateStatusPengaduan(String pengaduanId, String status) async {
|
||||||
|
try {
|
||||||
|
await client.from('pengaduan').update({
|
||||||
|
'status': status,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', pengaduanId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating status pengaduan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>?> getTindakanPengaduan(
|
||||||
|
String pengaduanId) async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('tindakan_pengaduan')
|
||||||
|
.select('*')
|
||||||
|
.eq('pengaduan_id', pengaduanId)
|
||||||
|
.order('created_at', ascending: false);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting tindakan pengaduan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Penerima bantuan methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getPenerimaBantuan() async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('warga').select('*');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting penerima bantuan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tambahPenerima(Map<String, dynamic> penerima) async {
|
||||||
|
try {
|
||||||
|
await client.from('warga').insert(penerima);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error adding penerima: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updatePenerima(
|
||||||
|
String penerimaId, Map<String, dynamic> penerima) async {
|
||||||
|
try {
|
||||||
|
await client.from('warga').update(penerima).eq('id', penerimaId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating penerima: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateStatusPenerima(String penerimaId, String status) async {
|
||||||
|
try {
|
||||||
|
await client.from('warga').update({
|
||||||
|
'status': status,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', penerimaId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating status penerima: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Laporan methods
|
||||||
|
Future<List<Map<String, dynamic>>?> getLaporan(
|
||||||
|
DateTime? tanggalMulai, DateTime? tanggalSelesai) async {
|
||||||
|
try {
|
||||||
|
var query = client.from('laporan').select('*');
|
||||||
|
|
||||||
|
if (tanggalMulai != null) {
|
||||||
|
query = query.gte('created_at', tanggalMulai.toIso8601String());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tanggalSelesai != null) {
|
||||||
|
query = query.lte('created_at', tanggalSelesai.toIso8601String());
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await query.order('created_at', ascending: false);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting laporan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> generateLaporan(Map<String, dynamic> laporan) async {
|
||||||
|
try {
|
||||||
|
final response = await client.from('laporan').insert(laporan);
|
||||||
|
|
||||||
|
return response[0]['id'];
|
||||||
|
} catch (e) {
|
||||||
|
print('Error generating laporan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> downloadLaporan(String laporanId) async {
|
||||||
|
try {
|
||||||
|
final response = await client
|
||||||
|
.from('laporan')
|
||||||
|
.select('file_urls')
|
||||||
|
.eq('id', laporanId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
final fileUrls = response['file_urls'];
|
||||||
|
if (fileUrls != null && fileUrls.isNotEmpty) {
|
||||||
|
return fileUrls[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error downloading laporan: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteLaporan(String laporanId) async {
|
||||||
|
try {
|
||||||
|
await client.from('laporan').delete().eq('id', laporanId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting laporan: $e');
|
||||||
|
throw e.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metode untuk mendapatkan data warga berdasarkan user ID
|
||||||
|
Future<Map<String, dynamic>?> getWargaByUserId() async {
|
||||||
|
try {
|
||||||
|
final user = currentUser;
|
||||||
|
if (user == null) return null;
|
||||||
|
|
||||||
|
final response = await client
|
||||||
|
.from('warga')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', user.id)
|
||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error getting warga data: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan data petugas desa
|
// Metode untuk membuat profil warga
|
||||||
Future<Map<String, dynamic>?> getPetugasDesaData() async {
|
|
||||||
if (currentUser == null) return null;
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx01_PetugasDesa')
|
|
||||||
.select()
|
|
||||||
.eq('userId', currentUser!.id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data donatur
|
|
||||||
Future<Map<String, dynamic>?> getDonaturData() async {
|
|
||||||
if (currentUser == null) return null;
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx01_Donatur')
|
|
||||||
.select()
|
|
||||||
.eq('userId', currentUser!.id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk membuat data warga
|
|
||||||
Future<void> createWargaProfile({
|
Future<void> createWargaProfile({
|
||||||
required String nik,
|
required String nik,
|
||||||
required String namaLengkap,
|
required String namaLengkap,
|
||||||
@ -138,131 +591,75 @@ class SupabaseService extends GetxService {
|
|||||||
DateTime? tanggalLahir,
|
DateTime? tanggalLahir,
|
||||||
String? agama,
|
String? agama,
|
||||||
}) async {
|
}) async {
|
||||||
if (currentUser == null) return;
|
try {
|
||||||
|
final user = currentUser;
|
||||||
|
if (user == null) throw 'User tidak ditemukan';
|
||||||
|
|
||||||
await client.from('xx02_Warga').insert({
|
await client.from('warga').insert({
|
||||||
'NIK': nik,
|
'user_id': user.id,
|
||||||
'namaLengkap': namaLengkap,
|
'nik': nik,
|
||||||
'jenisKelamin': jenisKelamin,
|
'nama_lengkap': namaLengkap,
|
||||||
'noHp': noHp,
|
'jenis_kelamin': jenisKelamin,
|
||||||
|
'no_hp': noHp,
|
||||||
'alamat': alamat,
|
'alamat': alamat,
|
||||||
'tempatLahir': tempatLahir,
|
'tempat_lahir': tempatLahir,
|
||||||
'tanggalLahir': tanggalLahir?.toIso8601String(),
|
'tanggal_lahir': tanggalLahir?.toIso8601String(),
|
||||||
'agama': agama,
|
'agama': agama,
|
||||||
'userId': currentUser!.id,
|
'status': 'MENUNGGU_VERIFIKASI',
|
||||||
'email': currentUser!.email,
|
'created_at': DateTime.now().toIso8601String(),
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update user profile role
|
||||||
|
await client.from('user_profile').upsert({
|
||||||
|
'id': user.id,
|
||||||
|
'role': 'WARGA',
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error creating warga profile: $e');
|
||||||
|
throw e.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan data warga berdasarkan userId
|
|
||||||
Future<Map<String, dynamic>?> getWargaByUserId() async {
|
|
||||||
if (currentUser == null) return null;
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx02_Warga')
|
|
||||||
.select()
|
|
||||||
.eq('userId', currentUser!.id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan notifikasi pengguna
|
// Metode untuk mendapatkan notifikasi pengguna
|
||||||
Future<List<Map<String, dynamic>>> getUserNotifications(
|
Future<List<Map<String, dynamic>>> getUserNotifications(
|
||||||
{bool unreadOnly = false}) async {
|
{bool unreadOnly = false}) async {
|
||||||
if (currentUser == null) return [];
|
try {
|
||||||
|
final user = currentUser;
|
||||||
|
if (user == null) return [];
|
||||||
|
|
||||||
final query = client.from('Notification').select();
|
final query = unreadOnly
|
||||||
|
? client
|
||||||
|
.from('notifikasi')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', user.id)
|
||||||
|
.eq('dibaca', false)
|
||||||
|
.order('created_at', ascending: false)
|
||||||
|
: client
|
||||||
|
.from('notifikasi')
|
||||||
|
.select('*')
|
||||||
|
.eq('user_id', user.id)
|
||||||
|
.order('created_at', ascending: false);
|
||||||
|
|
||||||
// Tambahkan filter untuk user ID
|
final response = await query;
|
||||||
final filteredQuery = query.eq('userId', currentUser!.id);
|
return response;
|
||||||
|
} catch (e) {
|
||||||
// Tambahkan filter untuk notifikasi yang belum dibaca jika diperlukan
|
print('Error getting user notifications: $e');
|
||||||
final finalQuery =
|
return [];
|
||||||
unreadOnly ? filteredQuery.eq('isRead', false) : filteredQuery;
|
}
|
||||||
|
|
||||||
// Tambahkan pengurutan
|
|
||||||
final response = await finalQuery.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk menandai notifikasi sebagai telah dibaca
|
// Metode untuk menandai notifikasi sebagai telah dibaca
|
||||||
Future<void> markNotificationAsRead(int notificationId) async {
|
Future<void> markNotificationAsRead(int notificationId) async {
|
||||||
await client
|
try {
|
||||||
.from('Notification')
|
await client.from('notifikasi').update({
|
||||||
.update({'isRead': true}).eq('notificationId', notificationId);
|
'dibaca': true,
|
||||||
|
'updated_at': DateTime.now().toIso8601String(),
|
||||||
|
}).eq('id', notificationId);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error marking notification as read: $e');
|
||||||
|
throw e.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metode untuk mendapatkan data verifikasi warga
|
|
||||||
Future<List<Map<String, dynamic>>> getVerifikasiDataWarga() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx02_VerifikasiDataWarga')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data pengajuan bantuan
|
|
||||||
Future<List<Map<String, dynamic>>> getPengajuanBantuan() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx02_PengajuanKelayakanBantuan')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data skema bantuan
|
|
||||||
Future<List<Map<String, dynamic>>> getSkemaBantuan() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx02_SkemaBantuan')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data penyaluran bantuan
|
|
||||||
Future<List<Map<String, dynamic>>> getPenyaluranBantuan() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx01_PenyaluranBantuan')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data penitipan bantuan
|
|
||||||
Future<List<Map<String, dynamic>>> getPenitipanBantuan() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx01_PenitipanBantuan')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metode untuk mendapatkan data pengaduan
|
|
||||||
Future<List<Map<String, dynamic>>> getPengaduan() async {
|
|
||||||
if (currentUser == null) return [];
|
|
||||||
|
|
||||||
final response = await client
|
|
||||||
.from('xx01_Pengaduan')
|
|
||||||
.select()
|
|
||||||
.order('CREATED_AT', ascending: false);
|
|
||||||
|
|
||||||
return List<Map<String, dynamic>>.from(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
lib/app/utils/date_formatter.dart
Normal file
27
lib/app/utils/date_formatter.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class DateFormatter {
|
||||||
|
static String formatDate(DateTime? date,
|
||||||
|
{String format = 'dd MMMM yyyy',
|
||||||
|
String locale = 'id_ID',
|
||||||
|
String defaultValue = '-'}) {
|
||||||
|
if (date == null) return defaultValue;
|
||||||
|
try {
|
||||||
|
return DateFormat(format, locale).format(date);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error formatting date: $e');
|
||||||
|
return date.toString().split(' ')[0]; // Fallback to basic format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatNumber(num? number,
|
||||||
|
{String locale = 'id_ID', String defaultValue = '0'}) {
|
||||||
|
if (number == null) return defaultValue;
|
||||||
|
try {
|
||||||
|
return NumberFormat("#,##0.##", locale).format(number);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error formatting number: $e');
|
||||||
|
return number.toString(); // Fallback to basic format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,14 +8,13 @@ class NavigationButton extends StatelessWidget {
|
|||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
const NavigationButton({
|
const NavigationButton({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.iconWidget,
|
this.iconWidget,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : assert(icon != null || iconWidget != null,
|
}) : assert(icon != null || iconWidget != null,
|
||||||
'Either icon or iconWidget must be provided'),
|
'Either icon or iconWidget must be provided');
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -8,12 +8,12 @@ class StatisticCard extends StatelessWidget {
|
|||||||
final double height;
|
final double height;
|
||||||
|
|
||||||
const StatisticCard({
|
const StatisticCard({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.count,
|
required this.count,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.height,
|
required this.height,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||||
|
import 'package:penyaluran_app/app/services/auth_service.dart';
|
||||||
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
import 'package:penyaluran_app/app/services/supabase_service.dart';
|
||||||
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
import 'package:penyaluran_app/app/theme/app_theme.dart';
|
||||||
|
import 'package:penyaluran_app/app/modules/auth/controllers/auth_controller.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Inisialisasi data locale untuk format tanggal
|
||||||
|
await initializeDateFormatting('id_ID', null);
|
||||||
|
|
||||||
// Inisialisasi Supabase
|
// Inisialisasi Supabase
|
||||||
await initServices();
|
await initServices();
|
||||||
|
|
||||||
@ -16,6 +23,10 @@ void main() async {
|
|||||||
// Inisialisasi service
|
// Inisialisasi service
|
||||||
Future<void> initServices() async {
|
Future<void> initServices() async {
|
||||||
await Get.putAsync(() => SupabaseService().init());
|
await Get.putAsync(() => SupabaseService().init());
|
||||||
|
await Get.putAsync(() => AuthService().init());
|
||||||
|
|
||||||
|
// Inisialisasi AuthController secara global
|
||||||
|
Get.put(AuthController(), permanent: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
@ -6,10 +6,14 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
#include <gtk/gtk_plugin.h>
|
#include <gtk/gtk_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
g_autoptr(FlPluginRegistrar) gtk_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
|
||||||
gtk_plugin_register_with_registrar(gtk_registrar);
|
gtk_plugin_register_with_registrar(gtk_registrar);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
flutter_secure_storage_linux
|
||||||
gtk
|
gtk
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
@ -6,12 +6,14 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import app_links
|
import app_links
|
||||||
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
|
80
pubspec.lock
80
pubspec.lock
@ -97,6 +97,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.8.0+1"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -134,6 +150,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_secure_storage:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage
|
||||||
|
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.2.4"
|
||||||
|
flutter_secure_storage_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_linux
|
||||||
|
sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
flutter_secure_storage_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_macos
|
||||||
|
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
flutter_secure_storage_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_platform_interface
|
||||||
|
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
flutter_secure_storage_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_web
|
||||||
|
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
flutter_secure_storage_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_secure_storage_windows
|
||||||
|
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
flutter_spinkit:
|
flutter_spinkit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -232,6 +296,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.7"
|
||||||
jwt_decode:
|
jwt_decode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -701,6 +773,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.10.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -55,6 +55,12 @@ dependencies:
|
|||||||
# Untuk format tanggal dalam bahasa Indonesia
|
# Untuk format tanggal dalam bahasa Indonesia
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
|
|
||||||
|
# HTTP client
|
||||||
|
dio: ^5.4.1
|
||||||
|
|
||||||
|
# Secure storage
|
||||||
|
flutter_secure_storage: ^9.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <app_links/app_links_plugin_c_api.h>
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
AppLinksPluginCApiRegisterWithRegistrar(
|
AppLinksPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||||
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
app_links
|
app_links
|
||||||
|
flutter_secure_storage_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user