221 lines
7.5 KiB
Dart
221 lines
7.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:intl/intl.dart'; // Pastikan ini ada untuk DateFormat
|
|
|
|
class HistoriPage extends StatefulWidget {
|
|
const HistoriPage({super.key});
|
|
|
|
@override
|
|
_HistoriPageState createState() => _HistoriPageState();
|
|
}
|
|
|
|
class _HistoriPageState extends State<HistoriPage> {
|
|
final ref = FirebaseDatabase.instance.ref("histori");
|
|
List<Map<String, dynamic>> logs = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Mendengarkan perubahan data pada node 'histori' di Firebase
|
|
ref.onValue.listen(
|
|
(event) {
|
|
if (event.snapshot.value != null && event.snapshot.value is Map) {
|
|
final data = Map<String, dynamic>.from(event.snapshot.value as Map);
|
|
final List<Map<String, dynamic>> temp = [];
|
|
|
|
data.forEach((key, value) {
|
|
if (value is Map) {
|
|
final log = Map<String, dynamic>.from(value);
|
|
log['key'] = key; // Simpan key Firebase
|
|
|
|
// --- PERBAIKAN PENTING: Parsing Timestamp ---
|
|
String timestampStr = log['timestamp']?.toString() ?? '';
|
|
DateTime logDateTime;
|
|
|
|
try {
|
|
// Coba parsing sebagai int (Unix timestamp dari Arduino)
|
|
int unixTimestamp = int.parse(timestampStr);
|
|
logDateTime = DateTime.fromMillisecondsSinceEpoch(
|
|
unixTimestamp * 1000,
|
|
);
|
|
} catch (e) {
|
|
// Jika parsing sebagai int gagal, coba parsing sebagai formatted string (dari log manual Flutter)
|
|
try {
|
|
logDateTime = DateFormat(
|
|
'yyyy-MM-dd HH:mm:ss',
|
|
).parse(timestampStr);
|
|
} catch (e) {
|
|
// Fallback jika keduanya gagal, gunakan waktu saat ini atau nilai default
|
|
logDateTime = DateTime.now();
|
|
print(
|
|
'Error parsing timestamp: $timestampStr. Using current time. Error: $e',
|
|
);
|
|
}
|
|
}
|
|
log['parsedTimestamp'] =
|
|
logDateTime; // Simpan DateTime yang sudah di-parse untuk pengurutan
|
|
|
|
temp.add(log);
|
|
}
|
|
});
|
|
|
|
// Urutkan berdasarkan 'parsedTimestamp' (DateTime) secara menurun (terbaru di atas)
|
|
temp.sort((a, b) {
|
|
final DateTime tsA = a['parsedTimestamp'];
|
|
final DateTime tsB = b['parsedTimestamp'];
|
|
return tsB.compareTo(tsA); // Urutkan dari yang terbaru ke terlama
|
|
});
|
|
|
|
setState(() {
|
|
logs = temp; // Perbarui state dengan data yang sudah diurutkan
|
|
});
|
|
} else {
|
|
// Jika snapshot.value null atau bukan Map (misal node dihapus/kosong)
|
|
setState(() {
|
|
logs = [];
|
|
});
|
|
}
|
|
},
|
|
onError: (error) {
|
|
// Penanganan error untuk listener Firebase
|
|
print("Error listening to Firebase history: $error");
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("❌ Gagal memuat histori: $error")),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// Fungsi untuk menghapus satu entri histori
|
|
void _hapusHistori(String key) {
|
|
ref
|
|
.child(key)
|
|
.remove()
|
|
.then((_) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("✅ Histori berhasil dihapus")),
|
|
);
|
|
})
|
|
.catchError((e) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text("❌ Gagal hapus histori: $e")));
|
|
});
|
|
}
|
|
|
|
// Fungsi konfirmasi untuk menghapus semua histori
|
|
void _konfirmasiHapusHistori() {
|
|
if (logs.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Tidak ada histori untuk dihapus.")),
|
|
);
|
|
return;
|
|
}
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text("Hapus Semua Histori"),
|
|
content: const Text("Apakah kamu yakin ingin menghapus semua histori?"),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text("Batal"),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
ref
|
|
.remove()
|
|
.then((_) {
|
|
Navigator.pop(ctx);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("✅ Semua histori dihapus")),
|
|
);
|
|
})
|
|
.catchError((e) {
|
|
Navigator.pop(ctx);
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("❌ Gagal hapus histori: $e")),
|
|
);
|
|
});
|
|
},
|
|
child: const Text("Hapus", style: TextStyle(color: Colors.red)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text("Histori Aktivitas"),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.delete, color: Colors.redAccent),
|
|
onPressed: _konfirmasiHapusHistori,
|
|
),
|
|
],
|
|
),
|
|
body: logs.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.history, size: 60, color: Colors.grey[400]),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
"Belum ada aktivitas.",
|
|
style: TextStyle(fontSize: 16, color: Colors.grey),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.separated(
|
|
padding: const EdgeInsets.all(8),
|
|
itemCount: logs.length,
|
|
separatorBuilder: (_, __) => const SizedBox(height: 4),
|
|
itemBuilder: (_, i) {
|
|
final log = logs[i];
|
|
// Menggunakan 'parsedTimestamp' untuk tampilan
|
|
final DateTime displayTimestamp =
|
|
log['parsedTimestamp'] as DateTime;
|
|
final String formattedTimestamp = DateFormat(
|
|
'dd MMM yyyy HH:mm:ss',
|
|
).format(displayTimestamp);
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: ListTile(
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 8,
|
|
),
|
|
leading: const Icon(Icons.access_time, color: Colors.blue),
|
|
title: Text(
|
|
log["aksi"] ?? "-",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
subtitle: Text(
|
|
formattedTimestamp, // Tampilkan timestamp yang sudah diformat
|
|
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
|
|
),
|
|
trailing: IconButton(
|
|
icon: const Icon(Icons.delete, color: Colors.red),
|
|
onPressed: () => _hapusHistori(log['key']),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|