oke
This commit is contained in:
parent
5602e488be
commit
21e471d0ea
|
|
@ -14,7 +14,6 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||
|
||||
// Import kondisional: web pakai dart:html, mobile pakai io + share_plus
|
||||
import 'excel_saver_stub.dart'
|
||||
if (dart.library.html) 'excel_saver_web.dart'
|
||||
if (dart.library.io) 'excel_saver_mobile.dart';
|
||||
|
|
@ -39,21 +38,23 @@ class WindSpeedExcelService {
|
|||
required String alertLevel,
|
||||
required String period,
|
||||
required List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
required String fileName, // ← nama custom dari user
|
||||
DateTime? dateFrom, // ← filter dari tanggal
|
||||
DateTime? dateTo, // ← filter sampai tanggal
|
||||
}) async {
|
||||
final excel = Excel.createExcel();
|
||||
excel.delete('Sheet1');
|
||||
|
||||
_buildSummarySheet(
|
||||
excel, currentSpeed, alertLevel, period, history, filterDate);
|
||||
_buildHistorySheet(excel, history, filterDate);
|
||||
excel, currentSpeed, alertLevel, period, history, dateFrom, dateTo);
|
||||
_buildHistorySheet(excel, history, dateFrom, dateTo);
|
||||
|
||||
// ── Simpan file ─────────────────────────────────────────
|
||||
final bytes = excel.save();
|
||||
if (bytes == null) throw Exception('Gagal membuat file Excel');
|
||||
final name = 'wind_speed_${_fileFmt.format(DateTime.now())}.xlsx';
|
||||
|
||||
await saveAndShareExcel(Uint8List.fromList(bytes), name);
|
||||
final safeName = fileName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_');
|
||||
await saveAndShareExcel(Uint8List.fromList(bytes), '$safeName.xlsx');
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
|
|
@ -65,7 +66,8 @@ class WindSpeedExcelService {
|
|||
String alertLevel,
|
||||
String period,
|
||||
List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
DateTime? dateFrom,
|
||||
DateTime? dateTo,
|
||||
) {
|
||||
final sheet = excel['Ringkasan'];
|
||||
|
||||
|
|
@ -87,12 +89,16 @@ class WindSpeedExcelService {
|
|||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
_setCell(sheet, 2, 1, period, bgColor: _colorNormal);
|
||||
|
||||
if (filterDate != null) {
|
||||
if (dateFrom != null && dateTo != null) {
|
||||
_setCell(sheet, 3, 0, 'Filter tanggal',
|
||||
bold: true, bgColor: _colorSubHead, fontColor: _colorWhite);
|
||||
_setCell(
|
||||
sheet, 3, 1, DateFormat('dd MMMM yyyy', 'id_ID').format(filterDate),
|
||||
bgColor: _colorNormal);
|
||||
sheet,
|
||||
3,
|
||||
1,
|
||||
'${DateFormat('dd MMMM yyyy', 'id_ID').format(dateFrom)} → ${DateFormat('dd MMMM yyyy', 'id_ID').format(dateTo)}',
|
||||
bgColor: _colorNormal,
|
||||
);
|
||||
}
|
||||
|
||||
// -- Kecepatan saat ini --
|
||||
|
|
@ -146,7 +152,8 @@ class WindSpeedExcelService {
|
|||
static void _buildHistorySheet(
|
||||
Excel excel,
|
||||
List<MyWindSpeed> history,
|
||||
DateTime? filterDate,
|
||||
DateTime? dateFrom,
|
||||
DateTime? dateTo,
|
||||
) {
|
||||
final sheet = excel['Data History'];
|
||||
|
||||
|
|
@ -167,14 +174,15 @@ class WindSpeedExcelService {
|
|||
centered: true);
|
||||
}
|
||||
|
||||
// -- Filter jika ada tanggal dipilih --
|
||||
final data = filterDate != null
|
||||
? history
|
||||
.where((e) =>
|
||||
e.timestamp.year == filterDate.year &&
|
||||
e.timestamp.month == filterDate.month &&
|
||||
e.timestamp.day == filterDate.day)
|
||||
.toList()
|
||||
// Filter berdasarkan rentang tanggal (inklusif kedua ujung)
|
||||
final data = (dateFrom != null && dateTo != null)
|
||||
? history.where((e) {
|
||||
final d =
|
||||
DateTime(e.timestamp.year, e.timestamp.month, e.timestamp.day);
|
||||
final from = DateTime(dateFrom.year, dateFrom.month, dateFrom.day);
|
||||
final to = DateTime(dateTo.year, dateTo.month, dateTo.day);
|
||||
return !d.isBefore(from) && !d.isAfter(to);
|
||||
}).toList()
|
||||
: history;
|
||||
|
||||
// -- Isi baris data --
|
||||
|
|
|
|||
|
|
@ -58,9 +58,217 @@ class _WindSpeedScreenState extends State<WindSpeedScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Export Excel ─────────────────────────────────────────────
|
||||
Future<void> _exportExcel(BuildContext context, WindSpeedState state) async {
|
||||
// ── Dialog export: nama file + date range ───────────────────
|
||||
Future<void> _showExportDialog(
|
||||
BuildContext context, WindSpeedState state) async {
|
||||
// Tentukan batas tanggal dari history
|
||||
DateTime firstDate = DateTime.now().subtract(const Duration(days: 365));
|
||||
DateTime lastDate = DateTime.now();
|
||||
if (state.history.isNotEmpty) {
|
||||
final sorted = [...state.history]
|
||||
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
firstDate = DateTime(sorted.first.timestamp.year,
|
||||
sorted.first.timestamp.month, sorted.first.timestamp.day);
|
||||
lastDate = DateTime(sorted.last.timestamp.year,
|
||||
sorted.last.timestamp.month, sorted.last.timestamp.day);
|
||||
}
|
||||
|
||||
// State dialog — pakai ValueNotifier supaya tidak perlu StatefulWidget terpisah
|
||||
final nameController = TextEditingController(
|
||||
text: 'kecepatan_angin_${DateFormat('ddMMyyyy').format(DateTime.now())}',
|
||||
);
|
||||
DateTime? dateFrom;
|
||||
DateTime? dateTo;
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setDialogState) {
|
||||
final fmt = DateFormat('dd MMM yyyy', 'id_ID');
|
||||
|
||||
Future<void> pickRange() async {
|
||||
final range = await showDateRangePicker(
|
||||
context: ctx,
|
||||
firstDate: firstDate,
|
||||
lastDate: lastDate,
|
||||
initialDateRange: dateFrom != null && dateTo != null
|
||||
? DateTimeRange(start: dateFrom!, end: dateTo!)
|
||||
: null,
|
||||
locale: const Locale('id', 'ID'),
|
||||
builder: (ctx, child) => Theme(
|
||||
data: Theme.of(ctx).copyWith(
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.blue.shade700,
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
if (range != null) {
|
||||
setDialogState(() {
|
||||
dateFrom = range.start;
|
||||
dateTo = range.end;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.file_download_outlined, color: Colors.blue.shade700),
|
||||
const SizedBox(width: 10),
|
||||
const Text('Export Excel',
|
||||
style:
|
||||
TextStyle(fontSize: 17, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 340,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// ── Nama file ────────────────────────────────
|
||||
const Text('Nama file',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black54)),
|
||||
const SizedBox(height: 6),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'nama_file',
|
||||
suffixText: '.xlsx',
|
||||
filled: true,
|
||||
fillColor: Colors.grey.shade100,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 10),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide:
|
||||
BorderSide(color: Colors.blue.shade600, width: 1.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// ── Range tanggal ────────────────────────────
|
||||
const Text('Filter rentang tanggal',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black54)),
|
||||
const SizedBox(height: 6),
|
||||
InkWell(
|
||||
onTap: pickRange,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.date_range_rounded,
|
||||
size: 18, color: Colors.blue.shade600),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateFrom != null && dateTo != null
|
||||
? '${fmt.format(dateFrom!)} → ${fmt.format(dateTo!)}'
|
||||
: 'Semua data (tanpa filter)',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: dateFrom != null
|
||||
? Colors.black87
|
||||
: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (dateFrom != null)
|
||||
GestureDetector(
|
||||
onTap: () => setDialogState(() {
|
||||
dateFrom = null;
|
||||
dateTo = null;
|
||||
}),
|
||||
child: Icon(Icons.close_rounded,
|
||||
size: 16, color: Colors.grey.shade500),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (dateFrom != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'${state.history.where((e) => !e.timestamp.isBefore(dateFrom!) && !e.timestamp.isAfter(dateTo!.add(const Duration(days: 1)))).length} data dalam rentang ini',
|
||||
style:
|
||||
TextStyle(fontSize: 11, color: Colors.blue.shade600),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text('Batal',
|
||||
style: TextStyle(color: Colors.grey.shade600)),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_doExport(context, state, nameController.text.trim(),
|
||||
dateFrom, dateTo);
|
||||
},
|
||||
icon: const Icon(Icons.download_rounded, size: 16),
|
||||
label: const Text('Export'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
nameController.dispose();
|
||||
}
|
||||
|
||||
// ── Proses export setelah dialog ditutup ────────────────────
|
||||
Future<void> _doExport(
|
||||
BuildContext context,
|
||||
WindSpeedState state,
|
||||
String customName,
|
||||
DateTime? dateFrom,
|
||||
DateTime? dateTo,
|
||||
) async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final fileName = customName.isEmpty
|
||||
? 'kecepatan_angin_${DateFormat('ddMMyyyy').format(DateTime.now())}'
|
||||
: customName;
|
||||
|
||||
try {
|
||||
messenger.showSnackBar(
|
||||
const SnackBar(
|
||||
|
|
@ -83,15 +291,17 @@ class _WindSpeedScreenState extends State<WindSpeedScreen> {
|
|||
alertLevel: state.alertLevel,
|
||||
period: state.selectedPeriod,
|
||||
history: state.history,
|
||||
filterDate: state.selectedDate,
|
||||
fileName: fileName,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
);
|
||||
|
||||
messenger.hideCurrentSnackBar();
|
||||
messenger.showSnackBar(SnackBar(
|
||||
content: const Row(children: [
|
||||
Icon(Icons.check_circle, color: Colors.white),
|
||||
SizedBox(width: 8),
|
||||
Text('File Excel berhasil dibuat!'),
|
||||
content: Row(children: [
|
||||
const Icon(Icons.check_circle, color: Colors.white),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: Text('$fileName.xlsx berhasil dibuat!')),
|
||||
]),
|
||||
backgroundColor: Colors.green.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
|
|
@ -129,7 +339,7 @@ class _WindSpeedScreenState extends State<WindSpeedScreen> {
|
|||
icon: const Icon(Icons.file_download_outlined),
|
||||
onPressed: state.history.isEmpty
|
||||
? null
|
||||
: () => _exportExcel(context, state),
|
||||
: () => _showExportDialog(context, state),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
|
|
|
|||
Loading…
Reference in New Issue