fitur petugas

This commit is contained in:
Andreas Malvino
2025-06-22 09:25:58 +07:00
parent c4dd4fdfa2
commit 8284c93aa5
48 changed files with 8688 additions and 3436 deletions

View File

@ -0,0 +1,88 @@
import 'package:supabase_flutter/supabase_flutter.dart';
import '../data/models/pembayaran_model.dart';
class PembayaranService {
final SupabaseClient _supabase = Supabase.instance.client;
/// Ambil data pembayaran antara [start] (inklusif) dan [end] (eksklusif).
Future<List<PembayaranModel>> _fetchBetween(
DateTime start,
DateTime end,
) async {
final data = await _supabase
.from('pembayaran')
.select('id, metode_pembayaran, total_pembayaran, waktu_pembayaran')
.gte('waktu_pembayaran', start.toIso8601String())
.lt('waktu_pembayaran', end.toIso8601String())
.order('waktu_pembayaran', ascending: true);
return (data as List<dynamic>)
.map((e) => PembayaranModel.fromJson(e as Map<String, dynamic>))
.toList();
}
/// Hitung statistik yang diminta.
Future<Map<String, dynamic>> fetchStats() async {
final now = DateTime.now();
// Rentang bulan ini: [awal bulan ini, awal bulan depan)
final thisMonthStart = DateTime(now.year, now.month, 1);
final nextMonthStart = DateTime(now.year, now.month + 1, 1);
// Bulan lalu: [awal bulan lalu, awal bulan ini)
final lastMonthStart = DateTime(now.year, now.month - 1, 1);
final thisMonthEnd = thisMonthStart;
// 6 bulan terakhir: [6 bulan lalu, sekarang]
final sixMonthsAgo = DateTime(now.year, now.month - 6, 1);
// 1) Data bulan ini & bulan lalu
final thisMonthData = await _fetchBetween(thisMonthStart, nextMonthStart);
final lastMonthData = await _fetchBetween(lastMonthStart, thisMonthEnd);
// 2) Data 6 bulan terakhir
final sixMonthsData = await _fetchBetween(sixMonthsAgo, nextMonthStart);
// 3) Hitung total pendapatan
double sum(List<PembayaranModel> list) =>
list.fold(0.0, (acc, e) => acc + e.totalPembayaran);
final totalThis = sum(thisMonthData);
final totalLast = sum(lastMonthData);
final totalSix = sum(sixMonthsData);
// 4) Persentase selisih (bulanan)
double percentDiff = 0.0;
if (totalLast != 0) {
percentDiff = ((totalThis - totalLast) / totalLast) * 100;
}
// 5) Total per metode (hanya dari bulan ini, misalnya)
double totTunai = 0.0, totTransfer = 0.0;
for (var p in thisMonthData) {
if (p.metodePembayaran.toLowerCase() == 'tunai') {
totTunai += p.totalPembayaran;
} else if (p.metodePembayaran.toLowerCase() == 'transfer') {
totTransfer += p.totalPembayaran;
}
}
// 6) Trend per month (6 months, oldest to newest)
List<double> trendPerMonth = [];
for (int i = 5; i >= 0; i--) {
final dt = DateTime(now.year, now.month - i, 1);
final dtNext = DateTime(now.year, now.month - i + 1, 1);
final monthData = await _fetchBetween(dt, dtNext);
trendPerMonth.add(sum(monthData));
}
return {
'totalThisMonth': totalThis,
'percentComparedLast': percentDiff,
'totalTunai': totTunai,
'totalTransfer': totTransfer,
'totalLastSixMonths': totalSix,
'trendPerMonth': trendPerMonth,
};
}
}

View File

@ -2,6 +2,8 @@ import 'package:get/get.dart';
import 'navigation_service.dart';
import '../data/providers/auth_provider.dart';
import '../modules/warga/controllers/warga_dashboard_controller.dart';
import '../data/providers/aset_provider.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
/// Abstract class untuk mengelola lifecycle service dan dependency
abstract class ServiceManager {
@ -26,6 +28,11 @@ abstract class ServiceManager {
Get.put(AuthProvider(), permanent: true);
}
// Register AsetProvider if not already registered
if (!Get.isRegistered<AsetProvider>()) {
Get.put(AsetProvider(), permanent: true);
}
// Register WargaDashboardController as a permanent controller
// This ensures it's always available for the drawer
registerWargaDashboardController();

View File

@ -0,0 +1,222 @@
import 'package:supabase_flutter/supabase_flutter.dart';
import '../data/models/rental_booking_model.dart';
import 'package:flutter/foundation.dart';
class SewaService {
final SupabaseClient _supabase = Supabase.instance.client;
Future<List<SewaModel>> fetchAllSewa() async {
// 1) Ambil semua sewa_aset
final sewaData =
await _supabase.from('sewa_aset').select('''
id,
user_id,
status,
waktu_mulai,
waktu_selesai,
tanggal_pemesanan,
tipe_pesanan,
kuantitas,
aset_id,
paket_id
''')
as List<dynamic>;
if (sewaData.isEmpty) return [];
// Konversi dasar ke map
final List<Map<String, dynamic>> rawSewa =
sewaData.map((e) => e as Map<String, dynamic>).toList();
// Kumpulkan semua ID
final sewaIds = rawSewa.map((e) => e['id'] as String).toList();
final userIds = rawSewa.map((e) => e['user_id'] as String).toSet().toList();
// Pisahkan aset dan paket IDs
final asetIds =
rawSewa
.where((e) => e['tipe_pesanan'] == 'tunggal')
.map((e) => e['aset_id'] as String?)
.whereType<String>()
.toSet()
.toList();
final paketIds =
rawSewa
.where((e) => e['tipe_pesanan'] == 'paket')
.map((e) => e['paket_id'] as String?)
.whereType<String>()
.toSet()
.toList();
// 2) Ambil tagihan_sewa
final tagihanData =
await _supabase
.from('tagihan_sewa')
.select('sewa_aset_id, total_tagihan, denda, tagihan_dibayar')
.filter('sewa_aset_id', 'in', '(${sewaIds.join(",")})')
as List<dynamic>;
final Map<String, Map<String, dynamic>> mapTagihan = {
for (var t in tagihanData)
t['sewa_aset_id'] as String: t as Map<String, dynamic>,
};
// 3) Ambil data warga_desa
final wargaData =
await _supabase
.from('warga_desa')
.select('user_id, nama_lengkap, no_hp, avatar')
.filter('user_id', 'in', '(${userIds.join(",")})')
as List<dynamic>;
debugPrint('DEBUG wargaData (raw): ' + wargaData.toString());
final Map<String, Map<String, String>> mapWarga = {
for (var w in wargaData)
(w['user_id'] as String): {
'nama': w['nama_lengkap'] as String? ?? '-',
'noHp': w['no_hp'] as String? ?? '-',
'avatar': w['avatar'] as String? ?? '-',
},
};
debugPrint('DEBUG mapWarga (mapped): ' + mapWarga.toString());
// 4) Ambil data aset (untuk tunggal)
Map<String, Map<String, String>> mapAset = {};
if (asetIds.isNotEmpty) {
final asetData =
await _supabase
.from('aset')
.select('id, nama')
.filter('id', 'in', '(${asetIds.join(",")})')
as List<dynamic>;
final fotoData =
await _supabase
.from('foto_aset')
.select('id_aset, foto_aset')
.filter('id_aset', 'in', '(${asetIds.join(",")})')
as List<dynamic>;
// Map aset id → nama
mapAset = {
for (var a in asetData)
(a['id'] as String): {'nama': a['nama'] as String},
};
// Ambil foto pertama per aset
for (var f in fotoData) {
final aid = f['id_aset'] as String;
if (mapAset.containsKey(aid) && mapAset[aid]!['foto'] == null) {
mapAset[aid]!['foto'] = f['foto_aset'] as String;
}
}
}
// 5) Ambil data paket (untuk paket)
Map<String, Map<String, String>> mapPaket = {};
if (paketIds.isNotEmpty) {
final paketData =
await _supabase
.from('paket')
.select('id, nama')
.filter('id', 'in', '(${paketIds.join(",")})')
as List<dynamic>;
final fotoData =
await _supabase
.from('foto_aset')
.select('id_paket, foto_aset')
.filter('id_paket', 'in', '(${paketIds.join(",")})')
as List<dynamic>;
mapPaket = {
for (var p in paketData)
(p['id'] as String): {'nama': p['nama'] as String},
};
for (var f in fotoData) {
final pid = f['id_paket'] as String;
if (mapPaket.containsKey(pid) && mapPaket[pid]!['foto'] == null) {
mapPaket[pid]!['foto'] = f['foto_aset'] as String;
}
}
}
// Debug print hasil query utama
debugPrint('sewaData: ' + sewaData.toString());
debugPrint('sewaData count: ' + sewaData.length.toString());
debugPrint('tagihanData: ' + tagihanData.toString());
debugPrint('wargaData: ' + wargaData.toString());
debugPrint('mapAset: ' + mapAset.toString());
debugPrint('mapPaket: ' + mapPaket.toString());
// 6) Gabungkan dan bangun SewaModel
final List<SewaModel> result = [];
for (var row in rawSewa) {
final id = row['id'] as String;
final tipe = row['tipe_pesanan'] as String;
final warga = mapWarga[row['user_id']];
final tagihan = mapTagihan[id];
if (warga == null || tagihan == null) {
// Skip data jika relasi tidak ditemukan
continue;
}
result.add(
SewaModel(
id: id,
userId: row['user_id'],
status: row['status'] as String,
waktuMulai: DateTime.parse(row['waktu_mulai'] as String),
waktuSelesai: DateTime.parse(row['waktu_selesai'] as String),
tanggalPemesanan: DateTime.parse(row['tanggal_pemesanan'] as String),
tipePesanan: tipe,
kuantitas: row['kuantitas'] as int,
asetId: tipe == 'tunggal' ? row['aset_id'] as String : null,
asetNama:
(row['aset_id'] != null &&
tipe == 'tunggal' &&
mapAset[row['aset_id']] != null)
? mapAset[row['aset_id']]!['nama']
: null,
asetFoto:
(row['aset_id'] != null &&
tipe == 'tunggal' &&
mapAset[row['aset_id']] != null)
? mapAset[row['aset_id']]!['foto']
: null,
paketId: tipe == 'paket' ? row['paket_id'] as String : null,
paketNama:
(row['paket_id'] != null &&
tipe == 'paket' &&
mapPaket[row['paket_id']] != null)
? mapPaket[row['paket_id']]!['nama']
: null,
paketFoto:
(row['paket_id'] != null &&
tipe == 'paket' &&
mapPaket[row['paket_id']] != null)
? mapPaket[row['paket_id']]!['foto']
: null,
totalTagihan:
tagihan['total_tagihan'] != null
? (tagihan['total_tagihan'] as num?)?.toDouble() ?? 0.0
: 0.0,
denda:
tagihan['denda'] != null
? (tagihan['denda'] as num?)?.toDouble()
: null,
dibayar:
tagihan['tagihan_dibayar'] != null
? (tagihan['tagihan_dibayar'] as num?)?.toDouble()
: null,
paidAmount:
tagihan['total_tagihan'] != null
? (tagihan['total_tagihan'] as num?)?.toDouble()
: null,
wargaNama: warga['nama'] ?? '-',
wargaNoHp: warga['noHp'] ?? '-',
wargaAvatar: warga['avatar'] ?? '-',
),
);
}
// Debug print hasil query result
debugPrint('SewaModel result: ' + result.toString());
debugPrint('SewaModel result count: ' + result.length.toString());
return result;
}
}