E32221349_Medibox/lib/history_page.dart

463 lines
16 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'bottom_navigation.dart';
class HistoryPage extends StatefulWidget {
const HistoryPage({super.key});
@override
State<HistoryPage> createState() => _HistoryPageState();
}
class _HistoryPageState extends State<HistoryPage> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final List<DocumentSnapshot> _historyDocs = [];
final int _limit = 10;
DocumentSnapshot? _lastDocument;
bool _isLoadingMore = false;
bool _hasMore = true;
bool _isInitialLoading = true;
final ScrollController _scrollController = ScrollController();
// Filter variables
int? filterDay;
int? filterMonth;
int? filterYear;
int? filterHour;
int? filterMinute;
@override
void initState() {
super.initState();
_loadInitialHistory();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _scrollListener() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100 &&
!_isLoadingMore &&
_hasMore) {
_loadMoreHistory();
}
}
Future<void> _loadInitialHistory() async {
final user = _auth.currentUser;
if (user == null) return;
try {
final querySnapshot =
await _firestore
.collection('users')
.doc(user.uid)
.collection('history')
.orderBy('timestamp', descending: true)
.limit(_limit)
.get();
setState(() {
_historyDocs.clear();
_historyDocs.addAll(querySnapshot.docs);
_lastDocument =
querySnapshot.docs.isNotEmpty ? querySnapshot.docs.last : null;
_hasMore = querySnapshot.docs.length == _limit;
_isInitialLoading = false;
});
} catch (e) {
print('Error loading initial history: $e');
setState(() {
_isInitialLoading = false;
});
}
}
Future<void> _loadMoreHistory() async {
if (!_hasMore || _isLoadingMore || _lastDocument == null) return;
setState(() => _isLoadingMore = true);
final user = _auth.currentUser;
if (user == null) return;
try {
final querySnapshot =
await _firestore
.collection('users')
.doc(user.uid)
.collection('history')
.orderBy('timestamp', descending: true)
.startAfterDocument(_lastDocument!)
.limit(_limit)
.get();
setState(() {
_historyDocs.addAll(querySnapshot.docs);
_lastDocument =
querySnapshot.docs.isNotEmpty
? querySnapshot.docs.last
: _lastDocument;
_hasMore = querySnapshot.docs.length == _limit;
_isLoadingMore = false;
});
} catch (e) {
print('Error loading more history: $e');
setState(() {
_isLoadingMore = false;
});
}
}
String _formatDate(dynamic timestamp) {
if (timestamp == null) return '';
DateTime dateTime;
if (timestamp is Timestamp) {
dateTime = timestamp.toDate().toLocal();
} else if (timestamp is String) {
dateTime = DateTime.tryParse(timestamp)?.toLocal() ?? DateTime.now();
} else {
return '';
}
final time =
"${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}";
final date =
"${dateTime.day.toString().padLeft(2, '0')}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.year}";
return "$time $date";
}
Future<void> _deleteHistory(String docId) async {
final user = _auth.currentUser;
if (user == null) return;
await _firestore
.collection('users')
.doc(user.uid)
.collection('history')
.doc(docId)
.delete();
setState(() {
_historyDocs.removeWhere((doc) => doc.id == docId);
});
}
Future<void> _refresh() async {
setState(() {
_isInitialLoading = true;
_hasMore = true;
_lastDocument = null;
});
await _loadInitialHistory();
}
List<DocumentSnapshot> applyFilter(List<DocumentSnapshot> docs) {
return docs.where((doc) {
final data = doc.data() as Map<String, dynamic>?;
if (data == null) return false;
final timestamp = data['timestamp'];
if (timestamp == null) return false;
DateTime dt;
if (timestamp is Timestamp) {
dt = timestamp.toDate();
} else if (timestamp is String) {
dt = DateTime.tryParse(timestamp) ?? DateTime.now();
} else {
return false;
}
if (filterYear != null && dt.year != filterYear) return false;
if (filterMonth != null && dt.month != filterMonth) return false;
if (filterDay != null && dt.day != filterDay) return false;
if (filterHour != null && dt.hour != filterHour) return false;
if (filterMinute != null && dt.minute != filterMinute) return false;
return true;
}).toList();
}
Widget buildDropdown(
String label,
List<int> items,
int? selectedValue,
ValueChanged<int?> onChanged,
) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('$label: '),
DropdownButton<int?>(
value: selectedValue,
hint: const Text('Semua'),
items:
[null, ...items].map<DropdownMenuItem<int?>>((val) {
return DropdownMenuItem<int?>(
value: val,
child: Text(val?.toString() ?? 'Semua'),
);
}).toList(),
onChanged: onChanged,
),
],
);
}
Widget buildFilterRow() {
final currentYear = DateTime.now().year;
return Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
buildDropdown(
'Tahun',
List.generate(10, (i) => currentYear - i),
filterYear,
(val) => setState(() => filterYear = val),
),
const SizedBox(width: 8),
buildDropdown(
'Bulan',
List.generate(12, (i) => i + 1),
filterMonth,
(val) => setState(() => filterMonth = val),
),
const SizedBox(width: 8),
buildDropdown(
'Tanggal',
List.generate(31, (i) => i + 1),
filterDay,
(val) => setState(() => filterDay = val),
),
const SizedBox(width: 8),
buildDropdown(
'Jam',
List.generate(24, (i) => i),
filterHour,
(val) => setState(() => filterHour = val),
),
const SizedBox(width: 8),
buildDropdown(
'Menit',
List.generate(60, (i) => i),
filterMinute,
(val) => setState(() => filterMinute = val),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: () {
setState(() {
filterDay = null;
filterMonth = null;
filterYear = null;
filterHour = null;
filterMinute = null;
});
},
child: const Text('Reset Filter'),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final user = _auth.currentUser;
if (user == null) {
return const Center(child: Text("User belum login."));
}
final filteredHistory = applyFilter(_historyDocs);
return Scaffold(
backgroundColor: const Color(0xFFE9F5EC),
body: SafeArea(
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(32),
bottomRight: Radius.circular(32),
),
),
child: const Text(
'History',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
buildFilterRow(),
Expanded(
child:
_isInitialLoading
? const Center(child: CircularProgressIndicator())
: filteredHistory.isEmpty
? const Center(child: Text("Belum ada riwayat."))
: RefreshIndicator(
onRefresh: _refresh,
child: ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount:
filteredHistory.length + (_hasMore ? 1 : 0),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
itemBuilder: (context, index) {
if (index >= filteredHistory.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: CircularProgressIndicator(),
),
);
}
final data =
filteredHistory[index].data()
as Map<String, dynamic>;
final docId = filteredHistory[index].id;
final kotak = data['kotak'] ?? 'Tidak diketahui';
final namaObat =
data['namaObat'] ?? 'Tidak diketahui';
final timestamp = data['timestamp'];
return Dismissible(
key: Key(docId),
direction: DismissDirection.endToStart,
background: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
alignment: Alignment.centerRight,
color: Colors.red,
child: const Icon(
Icons.delete,
color: Colors.white,
),
),
confirmDismiss: (_) async {
return await showDialog(
context: context,
builder:
(_) => AlertDialog(
title: const Text("Hapus History"),
content: const Text(
"Apakah kamu yakin ingin menghapus riwayat ini?",
),
actions: [
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(false),
child: const Text("Batal"),
),
TextButton(
onPressed:
() => Navigator.of(
context,
).pop(true),
child: const Text(
"Hapus",
style: TextStyle(
color: Colors.red,
),
),
),
],
),
);
},
onDismissed: (_) => _deleteHistory(docId),
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: EdgeInsets.symmetric(
vertical: 14,
horizontal: 16,
),
// padding: const EdgeInsets.symmetric(
// vertical: 14,
// horizontal: 16,
// ),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
"Kotak ${kotak.toString().split(' ').last}",
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
"Obat $namaObat",
style: const TextStyle(
fontSize: 20,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 6),
Text(
"Waktu : ${_formatDate(timestamp)}",
style: const TextStyle(
fontSize: 17,
color: Colors.black54,
),
),
],
),
),
);
},
),
),
),
],
),
),
);
}
}