219 lines
8.8 KiB
Dart
219 lines
8.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
|
|
class HistoryPage extends StatelessWidget {
|
|
final FirebaseFirestore firestore;
|
|
final bool canClearHistory;
|
|
|
|
const HistoryPage({
|
|
super.key,
|
|
required this.firestore,
|
|
this.canClearHistory = false,
|
|
});
|
|
|
|
Future<void> resetRiwayatAkses(BuildContext context) async {
|
|
final snapshot = await firestore.collection("access_logs").get();
|
|
final batch = firestore.batch();
|
|
|
|
for (var doc in snapshot.docs) {
|
|
batch.delete(doc.reference);
|
|
}
|
|
|
|
await batch.commit();
|
|
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Access logs successfully cleared")),
|
|
);
|
|
}
|
|
}
|
|
|
|
void konfirmasiResetDialog(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (_) => AlertDialog(
|
|
backgroundColor: const Color(0xff111111),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
title: const Text("Clear Logs?", style: TextStyle(color: Colors.white)),
|
|
content: const Text(
|
|
"Are you sure you want to delete all access history?",
|
|
style: TextStyle(color: Colors.white70),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: const Text("Cancel"),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
resetRiwayatAkses(context);
|
|
},
|
|
child: const Text("Clear All", style: TextStyle(color: Colors.redAccent)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.transparent,
|
|
floatingActionButton: canClearHistory
|
|
? FloatingActionButton(
|
|
backgroundColor: Colors.redAccent.withOpacity(0.9),
|
|
elevation: 8,
|
|
onPressed: () => konfirmasiResetDialog(context),
|
|
tooltip: "Reset History",
|
|
child: const Icon(Icons.delete_sweep_rounded, color: Colors.white, size: 28),
|
|
)
|
|
: null,
|
|
body: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Padding(
|
|
padding: EdgeInsets.only(bottom: 20),
|
|
child: Text(
|
|
'ACCESS HISTORY',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 32,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: StreamBuilder<QuerySnapshot>(
|
|
stream: firestore
|
|
.collection("access_logs")
|
|
.orderBy("timestamp", descending: true)
|
|
.snapshots(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator(color: Colors.blueAccent));
|
|
}
|
|
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
|
return const Center(
|
|
child: Text(
|
|
"No access logs available",
|
|
style: TextStyle(color: Colors.white38, fontSize: 16),
|
|
),
|
|
);
|
|
}
|
|
|
|
final logs = snapshot.data!.docs;
|
|
|
|
return ListView.builder(
|
|
itemCount: logs.length,
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.only(bottom: 80),
|
|
itemBuilder: (context, index) {
|
|
final log = logs[index].data() as Map<String, dynamic>;
|
|
|
|
String currentStatus = (log["status"] ?? "").toString();
|
|
final appStatuses = {"APP_OPEN", "APP_CLOSE"};
|
|
final manualStatuses = {"MANUAL_OPEN", "MANUAL_CLOSE"};
|
|
bool isGranted = appStatuses.contains(currentStatus) ||
|
|
manualStatuses.contains(currentStatus) ||
|
|
currentStatus == "ACCESS_GRANTED" ||
|
|
currentStatus == "SUCCESS" ||
|
|
currentStatus.contains("GRANT") ||
|
|
currentStatus.contains("SUCCESS") ||
|
|
currentStatus.contains("ACCEPT") ||
|
|
currentStatus.contains("OK");
|
|
String label;
|
|
if (appStatuses.contains(currentStatus) || manualStatuses.contains(currentStatus)) {
|
|
label = currentStatus.replaceAll('_', ' ');
|
|
} else {
|
|
label = isGranted ? "GRANTED" : "DENIED";
|
|
}
|
|
|
|
Timestamp? t = log["timestamp"] as Timestamp?;
|
|
String waktu = "-";
|
|
if (t != null) {
|
|
DateTime dt = t.toDate();
|
|
waktu = "${dt.day.toString().padLeft(2, '0')}/${dt.month.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}";
|
|
} else {
|
|
DateTime dt = DateTime.now();
|
|
waktu = "${dt.day.toString().padLeft(2, '0')}/${dt.month.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}";
|
|
}
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(15),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xff111111),
|
|
borderRadius: BorderRadius.circular(18),
|
|
border: Border.all(
|
|
color: isGranted ? Colors.green.withOpacity(0.2) : Colors.red.withOpacity(0.2),
|
|
width: 1.5,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: isGranted ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
isGranted ? Icons.check_circle_rounded : Icons.cancel_rounded,
|
|
color: isGranted ? Colors.greenAccent : Colors.redAccent,
|
|
size: 26,
|
|
),
|
|
),
|
|
const SizedBox(width: 15),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
log["name"] ?? "Unknown Card",
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
"UID: ${log["uid"]}",
|
|
style: const TextStyle(color: Colors.white54, fontSize: 13),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Text(
|
|
waktu,
|
|
style: const TextStyle(color: Colors.white38, fontSize: 12),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
color: isGranted ? Colors.greenAccent : Colors.redAccent,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |