feat: add feature bar chart to visualization trash perform
This commit is contained in:
parent
b997980f1c
commit
c87d820a9d
|
@ -11,9 +11,9 @@ export 'package:rijig_mobile/features/auth/service/otp_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_repository.dart';
|
export 'package:rijig_mobile/globaldata/trash/trash_repository.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_service.dart';
|
export 'package:rijig_mobile/globaldata/trash/trash_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
export 'package:rijig_mobile/globaldata/trash/trash_viewmodel.dart';
|
||||||
export 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
export 'package:rijig_mobile/globaldata/about/about_vmod.dart';
|
||||||
export 'package:rijig_mobile/features/home/repositories/about_repository.dart';
|
export 'package:rijig_mobile/globaldata/about/about_repository.dart';
|
||||||
export 'package:rijig_mobile/features/home/service/about_service.dart';
|
export 'package:rijig_mobile/globaldata/about/about_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/article/article_repository.dart';
|
export 'package:rijig_mobile/globaldata/article/article_repository.dart';
|
||||||
export 'package:rijig_mobile/globaldata/article/article_service.dart';
|
export 'package:rijig_mobile/globaldata/article/article_service.dart';
|
||||||
export 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
export 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||||
|
|
|
@ -45,6 +45,7 @@ final router = GoRouter(
|
||||||
|
|
||||||
// Rute untuk halaman-halaman utama
|
// Rute untuk halaman-halaman utama
|
||||||
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
||||||
|
GoRoute(path: '/dataperforma', builder: (context, state) => DatavisualizedScreen()),
|
||||||
GoRoute(path: '/activity', builder: (context, state) => ActivityScreen()),
|
GoRoute(path: '/activity', builder: (context, state) => ActivityScreen()),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/requestpickup',
|
path: '/requestpickup',
|
||||||
|
|
|
@ -3,6 +3,7 @@ export 'package:rijig_mobile/core/utils/navigation.dart';
|
||||||
export 'package:rijig_mobile/features/activity/presentation/screen/activity_screen.dart';
|
export 'package:rijig_mobile/features/activity/presentation/screen/activity_screen.dart';
|
||||||
export 'package:rijig_mobile/features/cart/presentation/screens/cart_screen.dart';
|
export 'package:rijig_mobile/features/cart/presentation/screens/cart_screen.dart';
|
||||||
export 'package:rijig_mobile/features/home/presentation/screen/home_screen.dart';
|
export 'package:rijig_mobile/features/home/presentation/screen/home_screen.dart';
|
||||||
|
export 'package:rijig_mobile/features/home/datavisualized/presentation/screen/datavisualized_screen.dart';
|
||||||
export 'package:rijig_mobile/features/profil/presentation/screen/profil_screen.dart';
|
export 'package:rijig_mobile/features/profil/presentation/screen/profil_screen.dart';
|
||||||
export 'package:rijig_mobile/features/requestpick/presentation/screen/requestpickup_screen.dart';
|
export 'package:rijig_mobile/features/requestpick/presentation/screen/requestpickup_screen.dart';
|
||||||
export 'package:rijig_mobile/features/auth/presentation/screen/inputpin_screen.dart';
|
export 'package:rijig_mobile/features/auth/presentation/screen/inputpin_screen.dart';
|
||||||
|
|
|
@ -0,0 +1,755 @@
|
||||||
|
import 'package:charts_painter/chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||||
|
import 'package:rijig_mobile/widget/appbar.dart';
|
||||||
|
import 'package:el_tooltip/el_tooltip.dart';
|
||||||
|
|
||||||
|
class DatavisualizedScreen extends StatefulWidget {
|
||||||
|
const DatavisualizedScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DatavisualizedScreen> createState() => _DatavisualizedScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatavisualizedScreenState extends State<DatavisualizedScreen> {
|
||||||
|
final List<double> dataSampahTerjual = [
|
||||||
|
15.5,
|
||||||
|
23.2,
|
||||||
|
18.7,
|
||||||
|
31.4,
|
||||||
|
28.9,
|
||||||
|
42.1,
|
||||||
|
35.8,
|
||||||
|
];
|
||||||
|
final List<String> namaHari = [
|
||||||
|
'Sen',
|
||||||
|
'Sel',
|
||||||
|
'Rab',
|
||||||
|
'Kam',
|
||||||
|
'Jum',
|
||||||
|
'Sab',
|
||||||
|
'Min',
|
||||||
|
];
|
||||||
|
|
||||||
|
int? selectedIndex;
|
||||||
|
final List<ElTooltipController> tooltipControllers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Initialize tooltip controllers
|
||||||
|
for (int i = 0; i < dataSampahTerjual.length; i++) {
|
||||||
|
tooltipControllers.add(ElTooltipController());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hideAllTooltips();
|
||||||
|
for (var controller in tooltipControllers) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple method to hide all tooltips
|
||||||
|
void _hideAllTooltips() {
|
||||||
|
for (var controller in tooltipControllers) {
|
||||||
|
try {
|
||||||
|
controller.hide();
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors - controller might already be disposed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main selection handler - used by both bar clicks and day name clicks
|
||||||
|
void _handleSelection(int index) {
|
||||||
|
// If same item is selected, deselect it
|
||||||
|
if (selectedIndex == index) {
|
||||||
|
_clearSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide all tooltips first
|
||||||
|
_hideAllTooltips();
|
||||||
|
|
||||||
|
// Update selection
|
||||||
|
setState(() {
|
||||||
|
selectedIndex = index;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show tooltip after a brief delay
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
|
if (mounted && selectedIndex == index) {
|
||||||
|
tooltipControllers[index].show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear selection and hide tooltips
|
||||||
|
void _clearSelection() {
|
||||||
|
_hideAllTooltips();
|
||||||
|
setState(() {
|
||||||
|
selectedIndex = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: CustomAppBar(judul: "Performa"),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Header summary card
|
||||||
|
_buildHeaderCard(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Chart section
|
||||||
|
_buildChartSection(),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
// Day labels (clickable)
|
||||||
|
_buildDayLabels(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Selected item info card
|
||||||
|
if (selectedIndex != null) _buildSelectedItemCard(),
|
||||||
|
|
||||||
|
// Statistics cards
|
||||||
|
_buildStatisticsCards(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeaderCard() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.blue.shade200),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.recycling, color: Colors.blue.shade700, size: 28),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Sampah Terjual Minggu Ini',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Total: ${dataSampahTerjual.reduce((a, b) => a + b).toStringAsFixed(1)} kg',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.blue.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildChartSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Grafik Penjualan Sampah (kg)',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
height: 300,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Chart
|
||||||
|
Chart<void>(
|
||||||
|
state: ChartState<void>(
|
||||||
|
behaviour: ChartBehaviour(
|
||||||
|
onItemClicked: (item) {
|
||||||
|
final clickedIndex = dataSampahTerjual.indexWhere(
|
||||||
|
(data) => data == item.item.value,
|
||||||
|
);
|
||||||
|
if (clickedIndex != -1) {
|
||||||
|
_handleSelection(clickedIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data: ChartData.fromList(
|
||||||
|
dataSampahTerjual
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map((entry) => ChartItem<void>(entry.value))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
itemOptions: BarItemOptions(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
barItemBuilder: (itemBuilderData) {
|
||||||
|
final isSelected = selectedIndex == itemBuilderData.itemIndex;
|
||||||
|
return BarItem(
|
||||||
|
color: isSelected ? Colors.orange.shade600 : primaryColor,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
backgroundDecorations: [
|
||||||
|
GridDecoration(
|
||||||
|
showVerticalGrid: false,
|
||||||
|
showHorizontalGrid: true,
|
||||||
|
horizontalAxisStep: 10,
|
||||||
|
gridColor: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
foregroundDecorations: [
|
||||||
|
SparkLineDecoration(
|
||||||
|
lineColor: Colors.orange.shade400,
|
||||||
|
lineWidth: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height: 250,
|
||||||
|
),
|
||||||
|
// Tooltips overlay
|
||||||
|
..._buildTooltipOverlays(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTooltipOverlays() {
|
||||||
|
return dataSampahTerjual.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final value = entry.value;
|
||||||
|
final chartWidth = MediaQuery.of(context).size.width - 64;
|
||||||
|
final barWidth = chartWidth / dataSampahTerjual.length;
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: (index * barWidth) + (barWidth / 2) - 20,
|
||||||
|
top: 50,
|
||||||
|
bottom: 50,
|
||||||
|
width: 40,
|
||||||
|
child: ElTooltip(
|
||||||
|
controller: tooltipControllers[index],
|
||||||
|
position: ElTooltipPosition.topCenter,
|
||||||
|
color: Colors.black87,
|
||||||
|
showArrow: true,
|
||||||
|
showModal: false,
|
||||||
|
showChildAboveOverlay: false,
|
||||||
|
content: _buildTooltipContent(index, value),
|
||||||
|
child: Container(width: 40, color: Colors.transparent),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTooltipContent(int index, double value) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
tooltipControllers[index].hide();
|
||||||
|
_showDetailDialog(index);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(minWidth: 100),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
namaHari[index],
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${value.toStringAsFixed(1)} kg',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white24,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'👆 Tap untuk detail',
|
||||||
|
style: TextStyle(color: Colors.white70, fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDayLabels() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: namaHari.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final isSelected = selectedIndex == index;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => _handleSelection(index),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? Colors.orange.shade100 : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
entry.value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: isSelected ? FontWeight.bold : FontWeight.w500,
|
||||||
|
color: isSelected ? Colors.orange.shade700 : Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSelectedItemCard() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: Colors.orange.shade200),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, color: Colors.orange.shade700),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Data Terpilih: ${namaHari[selectedIndex!]}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.orange.shade700,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Sampah terjual: ${dataSampahTerjual[selectedIndex!].toStringAsFixed(1)} kg',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.orange.shade600,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Close button
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _clearSelection,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
margin: const EdgeInsets.only(right: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.shade200,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.orange.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Detail button
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => _navigateToDetailPage(selectedIndex!),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.shade700,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Detail',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatisticsCards() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Rata-rata',
|
||||||
|
'${(dataSampahTerjual.reduce((a, b) => a + b) / dataSampahTerjual.length).toStringAsFixed(1)} kg',
|
||||||
|
Icons.trending_up,
|
||||||
|
Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Tertinggi',
|
||||||
|
'${dataSampahTerjual.reduce((a, b) => a > b ? a : b).toStringAsFixed(1)} kg',
|
||||||
|
Icons.star,
|
||||||
|
Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Terendah',
|
||||||
|
'${dataSampahTerjual.reduce((a, b) => a < b ? a : b).toStringAsFixed(1)} kg',
|
||||||
|
Icons.trending_down,
|
||||||
|
Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Hari Aktif',
|
||||||
|
'${dataSampahTerjual.where((data) => data > 0).length} hari',
|
||||||
|
Icons.calendar_today,
|
||||||
|
Colors.green,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: color.withValues(alpha: 0.3)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 24),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDetailDialog(int index) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.bar_chart, color: primaryColor),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text('Detail ${namaHari[index]}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildDialogInfoRow('Hari:', namaHari[index]),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildDialogInfoRow(
|
||||||
|
'Jumlah:',
|
||||||
|
'${dataSampahTerjual[index].toStringAsFixed(1)} kg',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildDialogInfoRow(
|
||||||
|
'Persentase:',
|
||||||
|
'${((dataSampahTerjual[index] / dataSampahTerjual.reduce((a, b) => a + b)) * 100).toStringAsFixed(1)}%',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildDialogInfoRow(
|
||||||
|
'Ranking:',
|
||||||
|
'#${_getRanking(index)} dari ${dataSampahTerjual.length} hari',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_navigateToDetailPage(index);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
child: const Text('Lihat Detail'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDialogInfoRow(String label, String value) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 80,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getRanking(int index) {
|
||||||
|
final sortedData = dataSampahTerjual
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => b.value.compareTo(a.value));
|
||||||
|
return sortedData.indexWhere((entry) => entry.key == index) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToDetailPage(int index) {
|
||||||
|
_hideAllTooltips();
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => DetailHariScreen(
|
||||||
|
hari: namaHari[index],
|
||||||
|
jumlahSampah: dataSampahTerjual[index],
|
||||||
|
index: index,
|
||||||
|
allData: dataSampahTerjual,
|
||||||
|
allDays: namaHari,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_clearSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DetailHariScreen extends StatelessWidget {
|
||||||
|
final String hari;
|
||||||
|
final double jumlahSampah;
|
||||||
|
final int index;
|
||||||
|
final List<double> allData;
|
||||||
|
final List<String> allDays;
|
||||||
|
|
||||||
|
const DetailHariScreen({
|
||||||
|
super.key,
|
||||||
|
required this.hari,
|
||||||
|
required this.jumlahSampah,
|
||||||
|
required this.index,
|
||||||
|
required this.allData,
|
||||||
|
required this.allDays,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final totalSampah = allData.reduce((a, b) => a + b);
|
||||||
|
final persentase = (jumlahSampah / totalSampah) * 100;
|
||||||
|
final ranking = _getRanking();
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Detail $hari'),
|
||||||
|
backgroundColor: primaryColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [primaryColor.withValues(alpha: 0.1), Colors.white],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: primaryColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.recycling,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Penjualan Sampah',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
hari,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'${jumlahSampah.toStringAsFixed(1)} kg',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Persentase: ${persentase.toStringAsFixed(1)}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Ranking: #$ranking dari ${allData.length} hari',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getRanking() {
|
||||||
|
final sortedData = allData
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => b.value.compareTo(a.value));
|
||||||
|
return sortedData.indexWhere((entry) => entry.key == index) + 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import 'package:carousel_slider/carousel_slider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/core/router.dart';
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
import 'package:rijig_mobile/core/utils/guide.dart';
|
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||||
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
import 'package:rijig_mobile/globaldata/about/about_vmod.dart';
|
||||||
import 'package:rijig_mobile/widget/skeletonize.dart';
|
import 'package:rijig_mobile/widget/skeletonize.dart';
|
||||||
|
|
||||||
class AboutComponent extends StatefulWidget {
|
class AboutComponent extends StatefulWidget {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
import 'package:rijig_mobile/globaldata/about/about_vmod.dart';
|
||||||
import 'package:rijig_mobile/widget/appbar.dart';
|
import 'package:rijig_mobile/widget/appbar.dart';
|
||||||
import 'package:rijig_mobile/widget/skeletonize.dart';
|
import 'package:rijig_mobile/widget/skeletonize.dart';
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,11 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
import 'package:iconsax_flutter/iconsax_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:rijig_mobile/core/router.dart';
|
||||||
import 'package:rijig_mobile/core/utils/guide.dart';
|
import 'package:rijig_mobile/core/utils/guide.dart';
|
||||||
import 'package:rijig_mobile/features/home/presentation/components/about_comp.dart';
|
import 'package:rijig_mobile/features/home/presentation/components/about_comp.dart';
|
||||||
import 'package:rijig_mobile/features/home/presentation/components/article_list.dart';
|
import 'package:rijig_mobile/features/home/presentation/components/article_list.dart';
|
||||||
import 'package:rijig_mobile/features/home/presentation/viewmodel/about_vmod.dart';
|
import 'package:rijig_mobile/globaldata/about/about_vmod.dart';
|
||||||
import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
import 'package:rijig_mobile/globaldata/article/article_vmod.dart';
|
||||||
import 'package:rijig_mobile/widget/card_withicon.dart';
|
import 'package:rijig_mobile/widget/card_withicon.dart';
|
||||||
|
|
||||||
|
@ -89,7 +90,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
icon: Iconsax.trash,
|
icon: Iconsax.trash,
|
||||||
text: 'Sampah',
|
text: 'Sampah',
|
||||||
number: '245 kg',
|
number: '245 kg',
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
router.push('/dataperforma');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
CardWithIcon(
|
CardWithIcon(
|
||||||
icon: Iconsax.timer,
|
icon: Iconsax.timer,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:rijig_mobile/core/api/api_services.dart';
|
import 'package:rijig_mobile/core/api/api_services.dart';
|
||||||
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
import 'package:rijig_mobile/globaldata/about/about_model.dart';
|
||||||
|
|
||||||
class AboutRepository {
|
class AboutRepository {
|
||||||
final Https _https = Https();
|
final Https _https = Https();
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:rijig_mobile/features/home/repositories/about_repository.dart';
|
import 'package:rijig_mobile/globaldata/about/about_repository.dart';
|
||||||
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
import 'package:rijig_mobile/globaldata/about/about_model.dart';
|
||||||
|
|
||||||
class AboutService {
|
class AboutService {
|
||||||
final AboutRepository _aboutRepository;
|
final AboutRepository _aboutRepository;
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rijig_mobile/features/home/service/about_service.dart';
|
import 'package:rijig_mobile/globaldata/about/about_service.dart';
|
||||||
import 'package:rijig_mobile/features/home/model/about_model.dart';
|
import 'package:rijig_mobile/globaldata/about/about_model.dart';
|
||||||
|
|
||||||
class AboutViewModel extends ChangeNotifier {
|
class AboutViewModel extends ChangeNotifier {
|
||||||
final AboutService _aboutService;
|
final AboutService _aboutService;
|
16
pubspec.lock
16
pubspec.lock
|
@ -49,6 +49,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
charts_painter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: charts_painter
|
||||||
|
sha256: "5314ef91979b59b60a869df86caba09e7a3ce56da1fb18530ea7bbd2da77fa70"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -169,6 +177,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.2"
|
version: "7.0.2"
|
||||||
|
el_tooltip:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: el_tooltip
|
||||||
|
sha256: "0860b00e9390a31dd98369dc16d3b6fa2668fc52df712bd00e86d8931787fc17"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -9,12 +9,14 @@ environment:
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
carousel_slider: ^5.0.0
|
carousel_slider: ^5.0.0
|
||||||
|
charts_painter: ^3.1.1
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
concentric_transition: ^1.0.3
|
concentric_transition: ^1.0.3
|
||||||
connectivity_plus: ^6.1.4
|
connectivity_plus: ^6.1.4
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
custom_refresh_indicator: ^4.0.1
|
custom_refresh_indicator: ^4.0.1
|
||||||
device_info_plus: ^11.4.0
|
device_info_plus: ^11.4.0
|
||||||
|
el_tooltip: ^2.2.1
|
||||||
fl_chart: ^1.0.0
|
fl_chart: ^1.0.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
Loading…
Reference in New Issue