1046 lines
41 KiB
Dart
1046 lines
41 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:praresi/presentation/controllers/home_controller.dart';
|
|
import 'package:praresi/presentation/controllers/riwayat_controller.dart';
|
|
import 'package:praresi/services/bubble_services.dart';
|
|
import 'package:praresi/utils/pdf_export_daily_a4.dart';
|
|
import 'package:praresi/utils/pdf_export_daily_a4_multi.dart';
|
|
import 'package:praresi/utils/pdf_export_daily_a5.dart';
|
|
import 'package:praresi/utils/pdf_export_daily_a5_multi.dart';
|
|
import 'package:praresi/utils/pdf_export_daily_a6.dart';
|
|
|
|
class HomeView extends StatefulWidget {
|
|
const HomeView({super.key});
|
|
|
|
@override
|
|
State<HomeView> createState() => _HomeViewState();
|
|
}
|
|
|
|
class _HomeViewState extends State<HomeView> with SingleTickerProviderStateMixin {
|
|
bool isBubbleActive = false;
|
|
String lastName = 'Pengguna';
|
|
String greeting = '';
|
|
IconData timeIcon = Icons.wb_sunny_outlined;
|
|
|
|
final DashboardController c = Get.put(DashboardController());
|
|
final riwayatC = Get.put(RiwayatController(), permanent: true);
|
|
|
|
|
|
late AnimationController _controller;
|
|
late Animation<double> _rotateAnim;
|
|
|
|
Future<void> _refreshData() async {
|
|
// Matikan dulu listener lama biar tidak dobel
|
|
c.onClose();
|
|
// Jalankan ulang listener realtime
|
|
c.onInit();
|
|
// Bisa juga muat ulang user data
|
|
await _loadUserData();
|
|
|
|
// Delay kecil supaya animasi refresh terasa smooth
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
}
|
|
|
|
void _showPrintFormatDialog(
|
|
BuildContext context,
|
|
String day,
|
|
String date,
|
|
dynamic controller,
|
|
) {
|
|
Get.bottomSheet(
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
child: Wrap(
|
|
children: [
|
|
const Center(
|
|
child: Text(
|
|
"Pilih Format Unduh",
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
ListTile(
|
|
leading: const Icon(Icons.picture_as_pdf, color: Colors.blue),
|
|
title: const Text("Format A4"),
|
|
onTap: () async {
|
|
Get.back();
|
|
|
|
try {
|
|
// 🔹 Parse tanggal
|
|
final parsedDate =
|
|
DateFormat('d MMMM yyyy', 'id_ID').parse(date);
|
|
|
|
// 🔹 Ambil data harian
|
|
await controller.fetchResiByDate(parsedDate);
|
|
|
|
// 🔹 Export PDF (1 halaman = 1 data)
|
|
final path = await PdfExportDailyA4_onedata.exportDailyToA4_onedata(
|
|
controller.dailyDetail,
|
|
parsedDate,
|
|
);
|
|
|
|
Get.snackbar(
|
|
"Berhasil",
|
|
"PDF disimpan di: $path",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green.shade100,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
"Error",
|
|
"Gagal mencetak data: $e",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red.shade100,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
ListTile(
|
|
leading: const Icon(Icons.picture_as_pdf, color: Colors.blue),
|
|
title: const Text("Format A5"),
|
|
onTap: () async {
|
|
Get.back();
|
|
|
|
try {
|
|
// 🔹 Parse tanggal "25 Oktober 2025" ke DateTime
|
|
final parsedDate =
|
|
DateFormat('d MMMM yyyy', 'id_ID').parse(date);
|
|
|
|
// 🔹 Ambil data harian
|
|
await controller.fetchResiByDate(parsedDate);
|
|
|
|
// 🔹 Cetak ke PDF format A4
|
|
final path = await PdfExportDailyA5_onedata.exportDailyToA5_onedata(
|
|
controller.dailyDetail,
|
|
parsedDate,
|
|
);
|
|
|
|
Get.snackbar(
|
|
"Berhasil",
|
|
"PDF disimpan di: $path",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green.shade100,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
"Error",
|
|
"Gagal mencetak data: $e",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red.shade100,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
// 🔹 FORMAT A6
|
|
ListTile(
|
|
leading: const Icon(Icons.picture_as_pdf, color: Colors.blue),
|
|
title: const Text("Format A6"),
|
|
onTap: () async {
|
|
Get.back();
|
|
try {
|
|
final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date);
|
|
await controller.fetchResiByDate(parsedDate);
|
|
|
|
final path = await PdfExportDailyA6.exportDailyToA6(
|
|
controller.dailyDetail,
|
|
parsedDate,
|
|
);
|
|
|
|
Get.snackbar(
|
|
"Berhasil",
|
|
"PDF disimpan di: $path",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green.shade100,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
"Error",
|
|
"Gagal mencetak data: $e",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red.shade100,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
// 🔹 FORMAT A4
|
|
ListTile(
|
|
leading: const Icon(Icons.picture_as_pdf, color: Colors.blue),
|
|
title: const Text("Format A4 (Multi)"),
|
|
onTap: () async {
|
|
Get.back();
|
|
try {
|
|
final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date);
|
|
await controller.fetchResiByDate(parsedDate);
|
|
|
|
final path = await PdfExportDailyA4.exportDailyToA4(
|
|
controller.dailyDetail,
|
|
parsedDate,
|
|
);
|
|
|
|
Get.snackbar(
|
|
"Berhasil",
|
|
"PDF disimpan di: $path",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green.shade100,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
"Error",
|
|
"Gagal mencetak data: $e",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red.shade100,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
// 🔹 FORMAT A5
|
|
ListTile(
|
|
leading: const Icon(Icons.picture_as_pdf, color: Colors.blue),
|
|
title: const Text("Format A5 (Multi)"),
|
|
onTap: () async {
|
|
Get.back();
|
|
try {
|
|
final parsedDate = DateFormat('d MMMM yyyy', 'id_ID').parse(date);
|
|
await controller.fetchResiByDate(parsedDate);
|
|
|
|
final path = await PdfExportDailyA5.exportDailyToA5(
|
|
controller.dailyDetail,
|
|
parsedDate,
|
|
);
|
|
|
|
Get.snackbar(
|
|
"Berhasil",
|
|
"PDF disimpan di: $path",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.green.shade100,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
"Error",
|
|
"Gagal mencetak data: $e",
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
backgroundColor: Colors.red.shade100,
|
|
);
|
|
}
|
|
},
|
|
),
|
|
|
|
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_setGreeting();
|
|
_loadUserData();
|
|
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 800),
|
|
);
|
|
|
|
_rotateAnim = Tween<double>(begin: 0, end: 2 * pi).animate(
|
|
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
|
);
|
|
}
|
|
|
|
void _salinFormat(BuildContext context) {
|
|
const formatText = '''
|
|
Penerima :
|
|
Alamat :
|
|
No. Wa :
|
|
Barang :
|
|
Total :
|
|
Pembayaran :
|
|
''';
|
|
|
|
Clipboard.setData(const ClipboardData(text: formatText));
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text("Format pengiriman berhasil disalin!"),
|
|
duration: Duration(seconds: 2),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _setGreeting() {
|
|
final hour = DateTime.now().hour;
|
|
if (hour < 11) {
|
|
greeting = 'Selamat Pagi,';
|
|
timeIcon = Icons.wb_twilight;
|
|
} else if (hour < 15) {
|
|
greeting = 'Selamat Siang,';
|
|
timeIcon = Icons.wb_sunny_outlined;
|
|
} else if (hour < 18) {
|
|
greeting = 'Selamat Sore,';
|
|
timeIcon = Icons.wb_sunny_sharp;
|
|
} else {
|
|
greeting = 'Selamat Malam,';
|
|
timeIcon = Icons.bedtime_rounded;
|
|
}
|
|
}
|
|
|
|
Future<void> _loadUserData() async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user == null) return;
|
|
try {
|
|
final doc = await FirebaseFirestore.instance.collection('users').doc(user.uid).get();
|
|
if (doc.exists) {
|
|
final name = (doc.data()?['name'] ?? '').toString().trim();
|
|
if (name.isNotEmpty) {
|
|
final parts = name.split(RegExp(r'\s+'));
|
|
setState(() {
|
|
lastName = parts.isNotEmpty ? parts.last : name;
|
|
});
|
|
}
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
void _animateIcon() {
|
|
if (_controller.isAnimating) return;
|
|
_controller.forward(from: 0);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final size = MediaQuery.of(context).size;
|
|
|
|
return Scaffold(
|
|
body: Container(
|
|
width: size.width,
|
|
height: size.height,
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Color(0xFF1976D2), Color(0xFFE3F2FD)],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: RefreshIndicator(
|
|
color: const Color(0xFF1976D2),
|
|
onRefresh: _refreshData,
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 28),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// === CARD SAPAAN ===
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.10),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 6),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
greeting,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.black87,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
lastName,
|
|
style: const TextStyle(
|
|
fontSize: 24,
|
|
color: Color(0xFF1976D2),
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
'Buat dan cetak alamat pengiriman Anda dengan cepat dan mudah!',
|
|
textAlign: TextAlign.justify,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.black54,
|
|
height: 1.3,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap: _animateIcon,
|
|
child: AnimatedBuilder(
|
|
animation: _rotateAnim,
|
|
builder: (context, child) {
|
|
return Transform.rotate(
|
|
angle: _rotateAnim.value,
|
|
child: Icon(
|
|
timeIcon,
|
|
color: const Color(0xFF1976D2),
|
|
size: 42,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// === CARD CETAK DAN BUBBLE SEJAJAR ===
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
height: 85,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.08),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () async {
|
|
final now = DateTime.now();
|
|
final day = DateFormat.EEEE('id_ID').format(now);
|
|
final date = DateFormat('d MMMM yyyy', 'id_ID').format(now);
|
|
|
|
final controller = Get.find<RiwayatController>();
|
|
|
|
// 🔥 Ambil data hari ini dulu
|
|
await controller.fetchResiByDate(now);
|
|
|
|
// 🔥 CEK DATA
|
|
if (controller.dailyDetail.isEmpty) {
|
|
Get.snackbar(
|
|
"Tidak Ada Data",
|
|
"Belum ada riwayat pengiriman hari ini.",
|
|
backgroundColor: Colors.orange.shade100,
|
|
colorText: Colors.black,
|
|
snackPosition: SnackPosition.BOTTOM,
|
|
);
|
|
return;
|
|
}
|
|
|
|
// ✅ Kalau ada data → lanjut
|
|
_showPrintFormatDialog(context, day, date, controller);
|
|
},
|
|
icon: const Icon(Icons.download),
|
|
label: const Text('Unduh Data Pengiriman'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: const Color(0xFF1976D2),
|
|
elevation: 0,
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
|
|
Expanded(
|
|
child: Container(
|
|
height: 85,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.08),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _salinFormat(context),
|
|
icon: const Icon(Icons.copy),
|
|
label: const Text('Salin Format'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: const Color(0xFF1976D2),
|
|
elevation: 0,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// === CARD PENDAPATAN HARI INI ===
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.10),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// Bagian kiri: teks
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Pendapatan Hari Ini',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
"${c.formatRupiah(c.totalPendapatan.value)}",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1976D2),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Bagian kanan: ikon uang
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: const Color(0xFF1976D2), // warna biru border
|
|
width: 1, // ketebalan border
|
|
),
|
|
),
|
|
child: const Icon(
|
|
Icons.attach_money_outlined,
|
|
color: Color(0xFF1976D2),
|
|
size: 32,
|
|
),
|
|
)
|
|
|
|
],
|
|
),
|
|
),
|
|
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// === CARD PENGIRIMAN BESAR ===
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.10),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 6),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Pengiriman',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: Color(0xFF1976D2),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
// KIRI: TOTAL
|
|
Expanded(
|
|
flex: 2,
|
|
child: Container(
|
|
height: 150,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: const Color(0xFF1976D2), width: 2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'Total',
|
|
style: TextStyle(
|
|
color: Color(0xFF1976D2),
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
"${c.totalPaket.value}",
|
|
style: TextStyle(
|
|
color: Color(0xFF1976D2),
|
|
fontSize: 36,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(width: 12),
|
|
|
|
// KANAN: COD & NON COD
|
|
Expanded(
|
|
flex: 2,
|
|
child: Column(
|
|
children: [
|
|
// ✅ Card COD
|
|
InkWell(
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.withOpacity(0.15),
|
|
shape: BoxShape.circle,
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
child: const Icon(
|
|
Icons.attach_money_rounded,
|
|
color: Colors.orange,
|
|
size: 48,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Pendapatan COD Hari Ini',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
"${c.formatRupiah(c.totalPendapatanCOD.value)}",
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.orange,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.orange,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
child: const Text(
|
|
'Tutup',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
child: Container(
|
|
height: 68,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.orange, width: 2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
"COD : ${c.totalPaketCOD.value}",
|
|
style: TextStyle(
|
|
color: Colors.orange,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ✅ Card Non-COD
|
|
InkWell(
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.withOpacity(0.15),
|
|
shape: BoxShape.circle,
|
|
),
|
|
padding: const EdgeInsets.all(16),
|
|
child: const Icon(
|
|
Icons.payments_rounded,
|
|
color: Colors.green,
|
|
size: 48,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Pendapatan Non-COD Hari Ini',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
"${c.formatRupiah(c.totalPendapatanNonCOD.value)}",
|
|
style: TextStyle(
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
child: const Text(
|
|
'Tutup',
|
|
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
child: Container(
|
|
height: 68,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.green, width: 2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
"Non COD : ${c.totalPaketNonCOD.value}",
|
|
style: TextStyle(
|
|
color: Colors.green,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// === CARD GRAFIK PENGIRIMAN ===
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.10),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
'Grafik Pengiriman Harian',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// === Grafik Garis ===
|
|
SizedBox(
|
|
height: 200,
|
|
child: Obx(() {
|
|
if (c.grafikPengiriman.isEmpty) {
|
|
return const Center(child: Text("Belum ada data pengiriman"));
|
|
}
|
|
|
|
final now = DateTime.now();
|
|
final startDate = now.subtract(const Duration(days: 6));
|
|
final todayIndex = 6;
|
|
|
|
final spots = List<FlSpot>.from(c.grafikPengiriman)
|
|
..sort((a, b) => a.x.compareTo(b.x));
|
|
|
|
return LineChart(
|
|
LineChartData(
|
|
minX: 0,
|
|
maxX: 6,
|
|
minY: 0,
|
|
maxY: spots.map((e) => e.y).reduce((a, b) => a > b ? a : b) + 1,
|
|
gridData: const FlGridData(show: true, drawVerticalLine: false),
|
|
titlesData: FlTitlesData(
|
|
bottomTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
getTitlesWidget: (value, meta) {
|
|
if (value < 0 || value > 6) return const SizedBox();
|
|
final date = startDate.add(Duration(days: value.toInt()));
|
|
final dayLabel =
|
|
DateFormat('E', 'id_ID').format(date); // Sen, Sel, dll
|
|
final isToday = value.toInt() == todayIndex;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(top: 6),
|
|
child: Text(
|
|
dayLabel,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight:
|
|
isToday ? FontWeight.bold : FontWeight.normal,
|
|
color: isToday ? Colors.red : Colors.black,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
leftTitles: AxisTitles(
|
|
sideTitles: SideTitles(
|
|
showTitles: true,
|
|
reservedSize: 32,
|
|
getTitlesWidget: (value, meta) {
|
|
if (value % 1 != 0) return const SizedBox();
|
|
return Text(
|
|
value.toInt().toString(),
|
|
style: const TextStyle(
|
|
fontSize: 10, color: Colors.black54),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
borderData: FlBorderData(
|
|
show: true,
|
|
border: const Border(
|
|
top: BorderSide.none,
|
|
right: BorderSide.none,
|
|
left: BorderSide(color: Colors.black12),
|
|
bottom: BorderSide(color: Colors.black12),
|
|
),
|
|
),
|
|
lineBarsData: [
|
|
LineChartBarData(
|
|
isCurved: true,
|
|
color: const Color(0xFF1976D2),
|
|
barWidth: 3,
|
|
isStrokeCapRound: true,
|
|
spots: spots,
|
|
belowBarData: BarAreaData(
|
|
show: true,
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
const Color(0xFF1976D2).withOpacity(0.3),
|
|
Colors.transparent
|
|
],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
),
|
|
dotData: FlDotData(
|
|
show: true,
|
|
getDotPainter: (spot, percent, barData, index) {
|
|
final isToday = spot.x == todayIndex.toDouble();
|
|
return FlDotCirclePainter(
|
|
radius: isToday ? 5 : 3,
|
|
color: isToday ? Colors.red : Colors.blueAccent,
|
|
strokeWidth: 2,
|
|
strokeColor: Colors.white,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// === Card Daftar Total Pengiriman per Hari ===
|
|
Obx(() {
|
|
if (c.grafikPengiriman.isEmpty) return const SizedBox();
|
|
|
|
final now = DateTime.now();
|
|
final startDate = now.subtract(const Duration(days: 6));
|
|
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[50],
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.black12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: List.generate(7, (index) {
|
|
final date = startDate.add(Duration(days: index));
|
|
final dayLabel = DateFormat('EEEE', 'id_ID').format(date);
|
|
final value = c.grafikPengiriman
|
|
.firstWhereOrNull((e) => e.x == index.toDouble())
|
|
?.y
|
|
.toInt() ??
|
|
0;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(dayLabel, style: const TextStyle(fontSize: 13)),
|
|
Text("$value resi",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold, fontSize: 13)),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
)
|
|
|
|
|
|
],
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |