741 lines
27 KiB
Dart
741 lines
27 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||
import 'package:intl/intl.dart';
|
||
import 'dart:async';
|
||
|
||
class HistoryPage extends StatefulWidget {
|
||
const HistoryPage({super.key});
|
||
|
||
@override
|
||
State<HistoryPage> createState() => _HistoryPageState();
|
||
}
|
||
|
||
class _HistoryPageState extends State<HistoryPage> {
|
||
DateTime? _selectedDate;
|
||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||
Timer? _refreshTimer;
|
||
int _refreshInterval = 1; // Default 1 menit
|
||
final DateFormat _dateFormat = DateFormat('HH:mm:ss');
|
||
Map<String, List<QueryDocumentSnapshot>> _groupedData = {};
|
||
Set<String> _expandedGroups = {};
|
||
|
||
// Tambahkan state untuk filter sensor
|
||
String _selectedSensor = 'Semua';
|
||
final List<String> _sensorOptions = [
|
||
'Semua',
|
||
'Suhu',
|
||
'Kelembaban',
|
||
'Cahaya',
|
||
'Hujan',
|
||
'Status Cuaca',
|
||
];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_startRefreshTimer();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_refreshTimer?.cancel();
|
||
super.dispose();
|
||
}
|
||
|
||
void _startRefreshTimer() {
|
||
_refreshTimer?.cancel(); // Cancel existing timer if any
|
||
_refreshTimer =
|
||
Timer.periodic(Duration(minutes: _refreshInterval), (timer) {
|
||
setState(() {
|
||
// Force rebuild to refresh data
|
||
});
|
||
});
|
||
}
|
||
|
||
// Fungsi untuk mengelompokkan data berdasarkan waktu
|
||
Map<String, List<QueryDocumentSnapshot>> _groupDataByTime(
|
||
List<QueryDocumentSnapshot> docs) {
|
||
Map<String, List<QueryDocumentSnapshot>> grouped = {};
|
||
|
||
for (var doc in docs) {
|
||
final data = doc.data() as Map<String, dynamic>;
|
||
final timestamp = (data['timestamp'] as Timestamp).toDate();
|
||
|
||
// Kelompokkan berdasarkan waktu lengkap (dd/MM/yyyy HH:mm)
|
||
String groupKey = DateFormat('dd/MM/yyyy HH:mm').format(timestamp);
|
||
|
||
if (!grouped.containsKey(groupKey)) {
|
||
grouped[groupKey] = [];
|
||
}
|
||
grouped[groupKey]!.add(doc);
|
||
}
|
||
|
||
return grouped;
|
||
}
|
||
|
||
// Fungsi untuk menampilkan detail data
|
||
void _showDataDetail(QueryDocumentSnapshot doc) {
|
||
print('TAP DETAIL');
|
||
final data = doc.data() as Map<String, dynamic>;
|
||
debugPrint('Data detail: ' + data.toString());
|
||
final timestamp = (data['timestamp'] as Timestamp?)?.toDate();
|
||
|
||
final suhu = (data['temperature'] ?? '-') as dynamic;
|
||
final kelembaban = (data['humidity'] ?? '-') as dynamic;
|
||
final cahaya = (data['light'] ?? '-') as dynamic;
|
||
final rainValue = (data['rain'] ?? '-') as dynamic;
|
||
final rainStatus = data['rain_status'] as String? ?? '-';
|
||
final weatherStatus = data['weather_status'] as String? ??
|
||
(cahaya is num ? _calculateWeatherStatus(cahaya.toDouble()) : '-');
|
||
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: Row(
|
||
children: [
|
||
const Icon(Icons.info_outline, color: Colors.blue),
|
||
const SizedBox(width: 8),
|
||
const Text('Detail Data Sensor'),
|
||
],
|
||
),
|
||
content: SingleChildScrollView(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Tampilkan isi data Map untuk debug
|
||
if (data.isEmpty)
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 8),
|
||
child: Text(
|
||
'Data kosong: \\${doc.id}',
|
||
style: const TextStyle(color: Colors.red),
|
||
),
|
||
)
|
||
else
|
||
Padding(
|
||
padding: const EdgeInsets.only(bottom: 8),
|
||
child: Text(
|
||
'Data: \\${data.toString()}',
|
||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||
),
|
||
),
|
||
_buildDetailRow(
|
||
'Waktu',
|
||
timestamp != null
|
||
? DateFormat('dd/MM/yyyy HH:mm:ss').format(timestamp)
|
||
: '-'),
|
||
const Divider(),
|
||
_buildDetailRow(
|
||
'Suhu',
|
||
suhu is num
|
||
? '${suhu.toDouble().toStringAsFixed(1)}°C'
|
||
: '-'),
|
||
_buildDetailRow('Status Suhu',
|
||
suhu is num ? _getStatusSuhu(suhu.toDouble()) : '-'),
|
||
const Divider(),
|
||
_buildDetailRow(
|
||
'Kelembaban',
|
||
kelembaban is num
|
||
? '${kelembaban.toDouble().toStringAsFixed(1)}%'
|
||
: '-'),
|
||
_buildDetailRow(
|
||
'Status Kelembaban',
|
||
kelembaban is num
|
||
? _getStatusKelembaban(kelembaban.toDouble())
|
||
: '-'),
|
||
const Divider(),
|
||
_buildDetailRow(
|
||
'Intensitas Cahaya',
|
||
cahaya is num
|
||
? '${cahaya.toDouble().toStringAsFixed(0)} lux'
|
||
: '-'),
|
||
_buildDetailRow('Status Cuaca', weatherStatus),
|
||
const Divider(),
|
||
_buildDetailRow('Curah Hujan',
|
||
rainValue is num ? '${rainValue.toStringAsFixed(0)}' : '-'),
|
||
_buildDetailRow(
|
||
'Status Hujan',
|
||
rainValue is num
|
||
? _getStatusHujan(rainValue.toDouble())
|
||
: '-'),
|
||
],
|
||
),
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text('Tutup'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
print('SHOW DIALOG DIPANGGIL');
|
||
}
|
||
|
||
Widget _buildDetailRow(String label, String value) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
SizedBox(
|
||
width: 120,
|
||
child: Text(
|
||
label,
|
||
style: const TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 14,
|
||
),
|
||
),
|
||
),
|
||
const Text(': ', style: TextStyle(fontWeight: FontWeight.bold)),
|
||
Expanded(
|
||
child: Text(
|
||
value,
|
||
style: const TextStyle(fontSize: 14),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSensorRow(String label, String value, String status) {
|
||
return Row(
|
||
children: [
|
||
Expanded(
|
||
flex: 2,
|
||
child: Text(
|
||
label,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
),
|
||
Expanded(
|
||
flex: 1,
|
||
child: Text(
|
||
value,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
if (status.isNotEmpty)
|
||
Expanded(
|
||
flex: 1,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||
decoration: BoxDecoration(
|
||
color: _getStatusColor(status).withOpacity(0.2),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: Text(
|
||
status,
|
||
style: TextStyle(
|
||
fontSize: 10,
|
||
fontWeight: FontWeight.bold,
|
||
color: _getStatusColor(status),
|
||
),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
// void _showRefreshIntervalDialog() {
|
||
// showDialog(
|
||
// context: context,
|
||
// builder: (context) => AlertDialog(
|
||
// title: const Text('Atur Interval Refresh'),
|
||
// content: Column(
|
||
// mainAxisSize: MainAxisSize.min,
|
||
// children: [
|
||
// const Text('Pilih interval refresh data (dalam menit):'),
|
||
// const SizedBox(height: 16),
|
||
// DropdownButton<int>(
|
||
// value: _refreshInterval,
|
||
// isExpanded: true,
|
||
// items: const [
|
||
// DropdownMenuItem(value: 1, child: Text('1 menit')),
|
||
// DropdownMenuItem(value: 2, child: Text('2 menit')),
|
||
// DropdownMenuItem(value: 5, child: Text('5 menit')),
|
||
// DropdownMenuItem(value: 10, child: Text('10 menit')),
|
||
// DropdownMenuItem(value: 15, child: Text('15 menit')),
|
||
// DropdownMenuItem(value: 30, child: Text('30 menit')),
|
||
// ],
|
||
// onChanged: (value) {
|
||
// if (value != null) {
|
||
// setState(() {
|
||
// _refreshInterval = value;
|
||
// });
|
||
// _startRefreshTimer();
|
||
// Navigator.pop(context);
|
||
// }
|
||
// },
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// actions: [
|
||
// TextButton(
|
||
// onPressed: () => Navigator.pop(context),
|
||
// child: const Text('Batal'),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
// );
|
||
// }
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Row(
|
||
children: [
|
||
Text(
|
||
'Riwayat Data',
|
||
style:
|
||
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||
),
|
||
],
|
||
),
|
||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||
actions: [
|
||
if (_selectedDate != null)
|
||
IconButton(
|
||
icon: const Icon(Icons.clear, color: Colors.white),
|
||
onPressed: () {
|
||
setState(() {
|
||
_selectedDate = null;
|
||
});
|
||
},
|
||
tooltip: 'Hapus Filter Tanggal',
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.calendar_today, color: Colors.white),
|
||
onPressed: () => _selectDate(context),
|
||
tooltip: 'Pilih Tanggal',
|
||
),
|
||
// IconButton(
|
||
// icon: const Icon(Icons.timer),
|
||
// onPressed: _showRefreshIntervalDialog,
|
||
// tooltip: 'Atur Interval Refresh',
|
||
// ),
|
||
],
|
||
),
|
||
body: Column(
|
||
children: [
|
||
// Expanded agar StreamBuilder tetap memenuhi sisa layar
|
||
Expanded(
|
||
child: StreamBuilder<QuerySnapshot>(
|
||
stream: _firestore
|
||
.collection('sensor_history')
|
||
.orderBy('timestamp', descending: true)
|
||
.where('timestamp',
|
||
isGreaterThanOrEqualTo: _selectedDate != null
|
||
? Timestamp.fromDate(DateTime(_selectedDate!.year,
|
||
_selectedDate!.month, _selectedDate!.day))
|
||
: Timestamp.fromDate(
|
||
DateTime.now().subtract(const Duration(days: 1))))
|
||
.where('timestamp',
|
||
isLessThan: _selectedDate != null
|
||
? Timestamp.fromDate(DateTime(_selectedDate!.year,
|
||
_selectedDate!.month, _selectedDate!.day + 1))
|
||
: Timestamp.fromDate(
|
||
DateTime.now().add(const Duration(days: 1))))
|
||
.limit(100) // Batasi jumlah data yang ditampilkan
|
||
.snapshots(includeMetadataChanges: true),
|
||
builder: (context, snapshot) {
|
||
if (snapshot.hasError) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(
|
||
Icons.error_outline,
|
||
color: Colors.red,
|
||
size: 60,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Error: ${snapshot.error}',
|
||
style: const TextStyle(color: Colors.red),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
|
||
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
||
return const Center(
|
||
child: Text(
|
||
'Tidak ada data riwayat',
|
||
style: TextStyle(fontSize: 16),
|
||
),
|
||
);
|
||
}
|
||
|
||
// Filter data berdasarkan tanggal yang dipilih
|
||
final filteredDocs = _selectedDate != null
|
||
? snapshot.data!.docs.where((doc) {
|
||
final data = doc.data() as Map<String, dynamic>;
|
||
final timestamp = data['timestamp'] as Timestamp;
|
||
final docDate = timestamp.toDate();
|
||
return docDate.year == _selectedDate!.year &&
|
||
docDate.month == _selectedDate!.month &&
|
||
docDate.day == _selectedDate!.day;
|
||
}).toList()
|
||
: snapshot.data!.docs;
|
||
|
||
if (filteredDocs.isEmpty) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
const Icon(
|
||
Icons.calendar_today,
|
||
size: 60,
|
||
color: Colors.grey,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Tidak ada data untuk tanggal ${_selectedDate != null ? DateFormat('EEEE, dd/MM/yyyy').format(_selectedDate!) : 'yang dipilih'}',
|
||
style: const TextStyle(fontSize: 16),
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// Tampilkan waktu update terakhir
|
||
final lastUpdate = (filteredDocs.first.data()
|
||
as Map<String, dynamic>)['timestamp'] as Timestamp;
|
||
final lastUpdateTime = lastUpdate.toDate();
|
||
|
||
return Column(
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.all(8.0),
|
||
child: Column(
|
||
children: [
|
||
Text(
|
||
'Terakhir diperbarui: ${DateFormat('HH:mm:ss').format(lastUpdateTime)}',
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
if (_selectedDate != null) ...[
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
'Tanggal: ${DateFormat('EEEE, dd/MM/yyyy').format(_selectedDate!)}',
|
||
style: const TextStyle(
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
Expanded(
|
||
child: ListView.builder(
|
||
itemCount: filteredDocs.length,
|
||
itemBuilder: (context, index) {
|
||
final doc = filteredDocs[index];
|
||
final data = doc.data() as Map<String, dynamic>;
|
||
final timestamp =
|
||
(data['timestamp'] as Timestamp).toDate();
|
||
final suhu = (data['temperature'] ?? 0.0) as num;
|
||
final kelembaban = (data['humidity'] ?? 0.0) as num;
|
||
final cahaya = (data['light'] ?? 0.0) as num;
|
||
final rainValue = (data['rain'] ?? 0.0) as num;
|
||
final weatherStatus =
|
||
_getStatusCuaca(cahaya.toDouble());
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8, vertical: 12),
|
||
child: InkWell(
|
||
borderRadius: BorderRadius.circular(24),
|
||
splashColor: Colors.blue.withOpacity(0.1),
|
||
highlightColor: Colors.blue.withOpacity(0.05),
|
||
onTap: () {}, // hanya untuk efek ripple
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
Colors.blue.shade50,
|
||
Colors.blue.shade100.withOpacity(0.7),
|
||
],
|
||
begin: Alignment.topLeft,
|
||
end: Alignment.bottomRight,
|
||
),
|
||
borderRadius: BorderRadius.circular(24),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.blueGrey.withOpacity(0.10),
|
||
blurRadius: 16,
|
||
offset: const Offset(0, 8),
|
||
),
|
||
],
|
||
),
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 22, vertical: 22),
|
||
child: Column(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.access_time,
|
||
color: Colors.blue, size: 22),
|
||
const SizedBox(width: 12),
|
||
Text(
|
||
DateFormat('dd/MM/yyyy HH:mm:ss')
|
||
.format(timestamp),
|
||
style: const TextStyle(
|
||
fontWeight: FontWeight.w900,
|
||
fontSize: 18,
|
||
color: Colors.blue,
|
||
letterSpacing: 0.5,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 18),
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.thermostat,
|
||
color: Colors.redAccent,
|
||
size: 20),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: _buildDetailRow('Suhu',
|
||
'${suhu.toDouble().toStringAsFixed(1)}°C')),
|
||
const SizedBox(width: 8),
|
||
_statusChip(
|
||
_getStatusSuhu(suhu.toDouble()),
|
||
_getStatusColor(_getStatusSuhu(
|
||
suhu.toDouble())),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.water_drop,
|
||
color: Colors.blueAccent,
|
||
size: 20),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: _buildDetailRow(
|
||
'Kelembaban',
|
||
'${kelembaban.toDouble().toStringAsFixed(1)}%')),
|
||
const SizedBox(width: 8),
|
||
_statusChip(
|
||
_getStatusKelembaban(
|
||
kelembaban.toDouble()),
|
||
_getStatusColor(
|
||
_getStatusKelembaban(
|
||
kelembaban.toDouble()))),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.wb_sunny,
|
||
color: Colors.orange, size: 20),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: _buildDetailRow('Cahaya',
|
||
'${cahaya.toDouble().toStringAsFixed(0)} lux')),
|
||
const SizedBox(width: 8),
|
||
_statusChip(weatherStatus,
|
||
_getStatusColor(weatherStatus)),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
const Icon(Icons.grain,
|
||
color: Colors.indigo, size: 20),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: _buildDetailRow(
|
||
'Curah Hujan',
|
||
'${rainValue.toStringAsFixed(0)}')),
|
||
const SizedBox(width: 8),
|
||
_statusChip(
|
||
_getStatusHujan(
|
||
rainValue.toDouble()),
|
||
_getStatusColor(_getStatusHujan(
|
||
rainValue.toDouble()))),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
String _getStatusHujan(double kadar) {
|
||
// Status hujan berdasarkan nilai sensor
|
||
return (kadar < 500) ? "Hujan" : "Tidak Hujan";
|
||
}
|
||
|
||
String _getStatusCuaca(double cahaya) {
|
||
// Status cahaya berdasarkan nilai LDR
|
||
return (cahaya < 700) ? "Terang" : "Gelap";
|
||
}
|
||
|
||
Color _getStatusColor(String status) {
|
||
// Warna untuk setiap status sensor
|
||
switch (status) {
|
||
case 'Hujan':
|
||
return Colors.blue.shade700;
|
||
case 'Tidak Hujan':
|
||
return Colors.orange;
|
||
case 'Terang':
|
||
return Colors.orange;
|
||
case 'Gelap':
|
||
return Colors.grey.shade800;
|
||
case 'Dingin':
|
||
return Colors.blue.shade700;
|
||
case 'Sejuk':
|
||
return Colors.blue.shade400;
|
||
case 'Normal':
|
||
return Colors.green;
|
||
case 'Hangat':
|
||
return Colors.orange.shade700;
|
||
case 'Panas':
|
||
return Colors.red;
|
||
case 'Kering':
|
||
return Colors.orange;
|
||
case 'Basah':
|
||
return Colors.blue;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
String _calculateWeatherStatus(double cahaya) {
|
||
// Status cuaca berdasarkan nilai LDR
|
||
return (cahaya < 700) ? "Terang" : "Gelap";
|
||
}
|
||
|
||
Color _getWeatherStatusColor(String status) {
|
||
switch (status) {
|
||
case 'Terang':
|
||
return Colors.orange;
|
||
case 'Gelap':
|
||
return Colors.grey.shade800;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
String _getStatusSuhu(double suhu) {
|
||
// Status suhu berdasarkan nilai sensor
|
||
if (suhu <= 20) return 'Dingin';
|
||
if (suhu <= 23) return 'Sejuk';
|
||
if (suhu <= 26) return 'Normal';
|
||
if (suhu <= 27) return 'Hangat';
|
||
return 'Panas';
|
||
}
|
||
|
||
String _getStatusKelembaban(double kelembaban) {
|
||
// Status kelembaban berdasarkan nilai sensor
|
||
if (kelembaban < 40) return 'Kering';
|
||
if (kelembaban > 80) return 'Basah';
|
||
return 'Normal';
|
||
}
|
||
|
||
Color _getStatusSuhuColor(String status) {
|
||
// Warna untuk setiap status suhu
|
||
switch (status) {
|
||
case 'Dingin':
|
||
return Colors.blue.shade700;
|
||
case 'Sejuk':
|
||
return Colors.blue.shade400;
|
||
case 'Normal':
|
||
return Colors.orange;
|
||
case 'Hangat':
|
||
return Colors.orange.shade700;
|
||
case 'Panas':
|
||
return Colors.red;
|
||
default:
|
||
return Colors.grey;
|
||
}
|
||
}
|
||
|
||
Future<void> _selectDate(BuildContext context) async {
|
||
final DateTime? picked = await showDatePicker(
|
||
context: context,
|
||
initialDate: _selectedDate ?? DateTime.now(),
|
||
firstDate: DateTime(2020),
|
||
lastDate: DateTime.now(),
|
||
builder: (context, child) {
|
||
return Theme(
|
||
data: Theme.of(context).copyWith(
|
||
colorScheme: ColorScheme.light(
|
||
primary: Theme.of(context).colorScheme.primary,
|
||
onPrimary: Colors.white,
|
||
surface: Colors.white,
|
||
onSurface: Colors.black,
|
||
),
|
||
),
|
||
child: child!,
|
||
);
|
||
},
|
||
);
|
||
if (picked != null) {
|
||
setState(() {
|
||
_selectedDate = picked;
|
||
});
|
||
}
|
||
}
|
||
|
||
Widget _statusChip(String label, Color color) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: color.withOpacity(0.15),
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Text(
|
||
label,
|
||
style: TextStyle(
|
||
color: color,
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|