Perbarui model LokasiPenyaluran dengan mengganti properti alamat menjadi alamatLengkap. Modifikasi tampilan dan controller di modul donatur dan petugas desa untuk menggunakan properti baru ini. Tambahkan fungsionalitas untuk mengelola lokasi penyaluran, termasuk penghapusan dan pengeditan lokasi. Perbarui rute aplikasi untuk menambahkan halaman lokasi penyaluran baru dan pastikan controller terdaftar dengan benar.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
507
lib/app/modules/petugas_desa/views/lokasi_penyaluran_view.dart
Normal file
507
lib/app/modules/petugas_desa/views/lokasi_penyaluran_view.dart
Normal file
@ -0,0 +1,507 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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/routes/app_pages.dart';
|
||||
|
||||
class LokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
const LokasiPenyaluranView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Lokasi Penyaluran'),
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
body: _buildLokasiPenyaluranList(),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => Get.toNamed(Routes.tambahLokasiPenyaluran),
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
icon: const Icon(Icons.add_location, color: Colors.white),
|
||||
label:
|
||||
const Text('Tambah Lokasi', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLokasiPenyaluranList() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => controller.loadLokasiPenyaluranData(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header dengan gradient yang lebih menarik
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Colors.blue.shade700,
|
||||
Colors.blue.shade500,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.shade200.withOpacity(0.5),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
'Daftar Lokasi Penyaluran',
|
||||
style: Theme.of(Get.context!)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'Kelola lokasi penyaluran bantuan untuk masyarakat dengan lebih mudah',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Counter jumlah lokasi
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Obx(() => Text(
|
||||
'${controller.lokasiPenyaluranCache.length} Lokasi Terdaftar',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Daftar Lokasi
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
if (controller.isLokasiLoading.value) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.lokasiPenyaluranCache.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.location_off,
|
||||
size: 64,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Belum ada lokasi penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Tambahkan lokasi penyaluran untuk memulai',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () =>
|
||||
Get.toNamed(Routes.tambahLokasiPenyaluran),
|
||||
icon: const Icon(Icons.add_location),
|
||||
label: const Text('Tambah Lokasi'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 100),
|
||||
itemCount: controller.lokasiPenyaluranCache.length +
|
||||
1, // +1 untuk footer
|
||||
itemBuilder: (context, index) {
|
||||
// Footer item
|
||||
if (index == controller.lokasiPenyaluranCache.length) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Text(
|
||||
'Tidak ada lokasi lainnya',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Colors.grey.shade500,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Item lokasi normal
|
||||
final lokasi = controller.lokasiPenyaluranCache.values
|
||||
.elementAt(index);
|
||||
final lokasiId =
|
||||
controller.lokasiPenyaluranCache.keys.elementAt(index);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () {
|
||||
// Tambahkan aksi ketika card diklik
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon Lokasi dengan latar belakang
|
||||
Hero(
|
||||
tag: 'lokasi_icon_$lokasiId',
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
Colors.blue.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.blue.shade700,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Informasi utama lokasi
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Hero(
|
||||
tag: 'lokasi_nama_$lokasiId',
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(
|
||||
lokasi.nama,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (lokasi.alamatLengkap != null &&
|
||||
lokasi.alamatLengkap!.isNotEmpty)
|
||||
Text(
|
||||
lokasi.alamatLengkap!,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
// Indikator koordinat tersedia
|
||||
if (lokasi.latitude != null &&
|
||||
lokasi.longitude != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 4.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.gps_fixed,
|
||||
size: 14,
|
||||
color: Colors.blue.shade500,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Koordinat tersedia',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color:
|
||||
Colors.blue.shade500,
|
||||
fontWeight:
|
||||
FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Badge "NEW" jika lokasi baru dibuat (kurang dari 3 hari)
|
||||
if (_isNewLocation(lokasi.createdAt))
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.shade500,
|
||||
borderRadius:
|
||||
BorderRadius.circular(12),
|
||||
),
|
||||
child: const Text(
|
||||
'BARU',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Divider dan tag kategori
|
||||
if (lokasi.isLokasiTitip ||
|
||||
(lokasi.desa != null &&
|
||||
lokasi.desa!.isNotEmpty))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Divider(color: Colors.grey.shade200),
|
||||
),
|
||||
|
||||
// Informasi tambahan dan tag
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
// Tag lokasi penitipan jika ada
|
||||
if (lokasi.isLokasiTitip)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade100,
|
||||
borderRadius:
|
||||
BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Colors.green.shade300),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle_outline,
|
||||
size: 16,
|
||||
color: Colors.green.shade800,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Lokasi Penitipan',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Tag informasi desa jika ada
|
||||
if (lokasi.desa != null &&
|
||||
lokasi.desa!.isNotEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius:
|
||||
BorderRadius.circular(30),
|
||||
border: Border.all(
|
||||
color: Colors.blue.shade200),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_city,
|
||||
size: 16,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
lokasi.desa!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Tombol aksi
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
// Tombol Hapus
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
// Aksi hapus lokasi
|
||||
controller.hapusLokasiPenyaluran(
|
||||
lokasiId);
|
||||
},
|
||||
borderRadius:
|
||||
BorderRadius.circular(30),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0, vertical: 4.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.delete_outline,
|
||||
color: Colors.red.shade600,
|
||||
size: 18),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Hapus',
|
||||
style: TextStyle(
|
||||
color: Colors.red.shade600,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper method untuk menentukan apakah lokasi baru dibuat (kurang dari 3 hari)
|
||||
bool _isNewLocation(DateTime createdAt) {
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(createdAt);
|
||||
return difference.inDays < 3;
|
||||
}
|
||||
}
|
@ -5,12 +5,16 @@ 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/calendar_view_widget.dart';
|
||||
import 'package:penyaluran_app/app/modules/petugas_desa/views/tambah_penyaluran_view.dart';
|
||||
import 'package:penyaluran_app/app/routes/app_pages.dart';
|
||||
|
||||
class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
const PenyaluranView({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Memastikan controller tersedia
|
||||
if (!Get.isRegistered<JadwalPenyaluranController>()) {
|
||||
Get.put(JadwalPenyaluranController());
|
||||
}
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
@ -84,11 +88,6 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
// Ringkasan jadwal
|
||||
_buildJadwalSummary(Get.context!),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Tombol untuk mengelola lokasi penyaluran
|
||||
_buildLokasiPenyaluranSection(),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Jadwal hari ini
|
||||
@ -237,240 +236,4 @@ class PenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Widget untuk menampilkan section lokasi penyaluran
|
||||
Widget _buildLokasiPenyaluranSection() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: Colors.blue.shade100, width: 1),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Lokasi Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// Menampilkan dialog daftar lokasi penyaluran
|
||||
_showLokasiPenyaluranDialog();
|
||||
},
|
||||
icon: const Icon(Icons.map, size: 16),
|
||||
label: const Text('Lihat Lokasi'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
side: BorderSide(color: Colors.blue.shade300),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Kelola lokasi penyaluran bantuan untuk masyarakat dengan lebih mudah',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => Get.toNamed(Routes.tambahLokasiPenyaluran),
|
||||
icon: const Icon(Icons.add_location, size: 16),
|
||||
label: const Text('Tambah Lokasi Penyaluran Baru'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade50,
|
||||
foregroundColor: Colors.blue.shade700,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(color: Colors.blue.shade200),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk menampilkan dialog daftar lokasi penyaluran
|
||||
void _showLokasiPenyaluranDialog() {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Daftar Lokasi Penyaluran',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close),
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: Get.height * 0.5,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Obx(() {
|
||||
if (controller.isLokasiLoading.value) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.lokasiPenyaluranCache.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_off,
|
||||
size: 48,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Belum ada lokasi penyaluran',
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.toNamed(Routes.tambahLokasiPenyaluran);
|
||||
},
|
||||
icon: const Icon(Icons.add_location),
|
||||
label: const Text('Tambah Lokasi'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: controller.lokasiPenyaluranCache.length,
|
||||
itemBuilder: (context, index) {
|
||||
final lokasi = controller.lokasiPenyaluranCache.values
|
||||
.elementAt(index);
|
||||
final lokasiId = controller.lokasiPenyaluranCache.keys
|
||||
.elementAt(index);
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
lokasi.nama,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (lokasi.alamat != null &&
|
||||
lokasi.alamat!.isNotEmpty)
|
||||
Text(lokasi.alamat!),
|
||||
Row(
|
||||
children: [
|
||||
if (lokasi.isLokasiTitip)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.shade100,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
'Lokasi Penitipan',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.green.shade800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Get.toNamed(Routes.tambahLokasiPenyaluran);
|
||||
},
|
||||
child: const Text('Tambah Lokasi Baru'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -386,6 +386,15 @@ class PetugasDesaView extends GetView<PetugasDesaController> {
|
||||
Get.toNamed('/daftar-donatur');
|
||||
},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.location_on_outlined,
|
||||
activeIcon: Icons.location_on,
|
||||
title: 'Lokasi Penyaluran',
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
Get.toNamed('/lokasi-penyaluran');
|
||||
},
|
||||
),
|
||||
_buildMenuItem(
|
||||
icon: Icons.description_outlined,
|
||||
activeIcon: Icons.description,
|
||||
|
@ -15,16 +15,28 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
backgroundColor: AppTheme.primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
body: _buildTambahLokasiPenyaluranForm(context),
|
||||
body: _LokasiPenyaluranForm(controller: controller),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTambahLokasiPenyaluranForm(BuildContext context) {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final TextEditingController namaController = TextEditingController();
|
||||
final TextEditingController alamatLengkapController =
|
||||
TextEditingController();
|
||||
class _LokasiPenyaluranForm extends StatefulWidget {
|
||||
final JadwalPenyaluranController controller;
|
||||
|
||||
const _LokasiPenyaluranForm({required this.controller});
|
||||
|
||||
@override
|
||||
State<_LokasiPenyaluranForm> createState() => _LokasiPenyaluranFormState();
|
||||
}
|
||||
|
||||
class _LokasiPenyaluranFormState extends State<_LokasiPenyaluranForm> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final TextEditingController namaController = TextEditingController();
|
||||
final TextEditingController alamatLengkapController = TextEditingController();
|
||||
bool isLokasiTitip = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
@ -95,6 +107,29 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Checkbox Is Lokasi Titip
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isLokasiTitip,
|
||||
activeColor: AppTheme.primaryColor,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
isLokasiTitip = newValue ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text('Lokasi Titip'),
|
||||
const SizedBox(width: 4),
|
||||
const Tooltip(
|
||||
message:
|
||||
'Centang jika lokasi ini merupakan lokasi yang dapat menerima penitipan',
|
||||
child: Icon(Icons.info_outline, size: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Tombol Submit
|
||||
@ -107,6 +142,7 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
_tambahLokasiPenyaluran(
|
||||
nama: namaController.text,
|
||||
alamatLengkap: alamatLengkapController.text,
|
||||
isLokasiTitip: isLokasiTitip,
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -137,6 +173,7 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
Future<void> _tambahLokasiPenyaluran({
|
||||
required String nama,
|
||||
required String alamatLengkap,
|
||||
required bool isLokasiTitip,
|
||||
}) async {
|
||||
try {
|
||||
// Tampilkan loading
|
||||
@ -152,7 +189,8 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
final String id = uuid.v4();
|
||||
|
||||
// Ambil ID petugas desa yang sedang login dari controller
|
||||
final String? petugasDesaId = controller.supabaseService.currentUser?.id;
|
||||
final String? petugasDesaId =
|
||||
widget.controller.supabaseService.currentUser?.id;
|
||||
|
||||
if (petugasDesaId == null) {
|
||||
Get.back(); // Tutup dialog loading
|
||||
@ -167,7 +205,7 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
|
||||
// Dapatkan desa_id dari data petugas desa
|
||||
// Ambil data petugas desa dari Supabase untuk mendapatkan desa_id
|
||||
final petugasDesaData = await controller.supabaseService.client
|
||||
final petugasDesaData = await widget.controller.supabaseService.client
|
||||
.from('petugas_desa')
|
||||
.select('desa_id')
|
||||
.eq('id', petugasDesaId)
|
||||
@ -193,11 +231,12 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
'nama': nama,
|
||||
'alamat_lengkap': alamatLengkap,
|
||||
'desa_id': desaId,
|
||||
'is_lokasi_titip': isLokasiTitip,
|
||||
'created_at': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
// Insert data ke tabel lokasi_penyaluran
|
||||
await controller.supabaseService.client
|
||||
await widget.controller.supabaseService.client
|
||||
.from('lokasi_penyaluran')
|
||||
.insert(data);
|
||||
|
||||
@ -216,7 +255,7 @@ class TambahLokasiPenyaluranView extends GetView<JadwalPenyaluranController> {
|
||||
Get.back();
|
||||
|
||||
// Refresh data di controller
|
||||
controller.refreshData();
|
||||
widget.controller.refreshData();
|
||||
} catch (e) {
|
||||
// Tutup dialog loading
|
||||
Get.back();
|
||||
|
Reference in New Issue
Block a user