You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

865 lines
20 KiB

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:bookatanker/supplier/todaysprice/custom_calendar.dart';
import '../../common/settings.dart';
// Contains date picker + filtered graph
class GraphScreen extends StatefulWidget {
final int selectedType;
const GraphScreen({super.key, required this.selectedType});
@override
State<GraphScreen> createState() => _GraphScreenState();
}
class _GraphScreenState extends State<GraphScreen> {
List<Map<String, dynamic>> filteredDrinking = [];
List<Map<String, dynamic>> filteredBore = [];
DateTimeRange? selectedRange;
@override
void initState() {
super.initState();
_loadLastWeek(); // Load last week by default
}
void _loadLastWeek() {
DateTime today = DateTime.now();
DateTime lastWeek = today.subtract(const Duration(days: 7));
debugPrint('$lastWeek');
// Update selectedRange so the card shows last week
selectedRange = DateTimeRange(start: lastWeek, end: today);
_filterData(lastWeek, today);
setState(() {}); // trigger rebuild
}
void _pickDateRange() async {
final result = await showDialog<DateTimeRange>(
context: context,
builder: (context) => AdvancedDateRangePicker(
initialRange: selectedRange,
),
);
if (result != null) {
selectedRange = result;
_filterData(result.start, result.end);
setState(() {
selectedRange = result; // store this for showing in card above graph
});
}
}
void _filterData(DateTime start, DateTime end) {
setState(() {
filteredDrinking = weeklyPriceData["Drinking Water"]!
.where((d) {
DateTime date = DateTime.parse(d["date"]);
return !date.isBefore(start) && !date.isAfter(end);
}).toList();
filteredBore = weeklyPriceData["bore Water"]!
.where((d) {
DateTime date = DateTime.parse(d["date"]);
return !date.isBefore(start) && !date.isAfter(end);
}).toList();
});
}
void _openDateMenu() {
showMenu(
context: context,
color: Colors.white,
elevation: 6, // smooth shadow
position: const RelativeRect.fromLTRB(1000, 100, 10, 0), // position near icon
items: [
PopupMenuItem(
value: "week",
child: Text("Last Week",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
PopupMenuItem(
value: "month",
child: Text("Last Month",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
PopupMenuItem(
value: "custom",
child: Text("Custom Dates",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
],
).then((value) {
if (value == null) return;
if (value == "week") {
DateTime today = DateTime.now();
DateTime lastWeek = today.subtract(const Duration(days: 7));
setState(() {
selectedRange = DateTimeRange(start: lastWeek, end: today);
_filterData(lastWeek, today);
});
}
else if (value == "month") {
DateTime today = DateTime.now();
DateTime lastMonth = DateTime(today.year, today.month - 1, today.day);
setState(() {
selectedRange = DateTimeRange(start: lastMonth, end: today);
_filterData(lastMonth, today);
});
}
else if (value == "custom") {
_pickDateRange();
}
});
}
// For showing date in card above graph
String _formattedDateRange() {
if (selectedRange == null) return "Select Date";
final start = selectedRange!.start;
final end = selectedRange!.end;
const months = [
"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
if (start.month == end.month) {
return "${start.day} ${end.day} ${months[start.month]}";
} else {
return "${start.day} ${months[start.month]} ${end.day} ${months[end.month]}";
}
}
String _formattedAvgPrice() {
List<Map<String, dynamic>> activeList =
widget.selectedType == 0 ? filteredDrinking : filteredBore;
debugPrint('$activeList');
if (activeList.isEmpty) return "-";
double total = 0;
for (var item in activeList) {
total += (item["price"] as num).toDouble();
}
double avg = total / activeList.length;
return "${avg.toStringAsFixed(2)}";
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 🔥 TOP ROW (LEFT: BOX, RIGHT: CALENDAR)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// LEFT BOX
Padding(
padding: const EdgeInsets.only(top: 14, left: 10),
child: Container(
height: 100,
width: 180,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 8,
spreadRadius: 2,
offset: const Offset(2, 4),
),
],
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.2, 0), // Soft slide from right
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Column(
key: ValueKey(widget.selectedType), // IMPORTANT 🔑
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.selectedType == 0 ? "Drinking Water" : "Bore Water",
style: fontTextStyle(14, Colors.black, FontWeight.w600),
),
const SizedBox(height: 4),
Text(
_formattedDateRange(),
style: fontTextStyle(14, Colors.grey.shade700, FontWeight.w500),
),
const SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: "${_formattedAvgPrice()} ",
style: fontTextStyle(20, Colors.black, FontWeight.bold),
),
TextSpan(
text: "/5000 L",
style: fontTextStyle(14, Colors.grey.shade600, FontWeight.w400),
),
],
),
),
],
),
),
),
),
// RIGHT CALENDAR BUTTON
Padding(
padding: const EdgeInsets.only(top: 4, right: 8),
child: InkWell(
onTap: () => _openDateMenu(),
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Row(
children: const [
Icon(Icons.calendar_month, size: 28, color: Colors.blueAccent),
Icon(Icons.arrow_drop_down, size: 28, color: Colors.blueAccent),
],
),
),
),
),
],
),
const SizedBox(height: 5),
// Show graph only if data is available
if (filteredDrinking.isNotEmpty || filteredBore.isNotEmpty)
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: DoublePriceBarGraph(
drinkingData: filteredDrinking,
boreData: filteredBore,
),
),
)
else
Padding(
padding: EdgeInsets.all(30),
child: Text("No data available for the selected dates",
style: fontTextStyle(13, Colors.black87, FontWeight.w600),
),
),
],
);
}
}
Widget buildLegend() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Drinking Water Legend
Row(
children: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 6),
Text(
"Drinking Water",
style: fontTextStyle(12, Colors.black87, FontWeight.w600),
),
],
),
const SizedBox(width: 20),
// Bore Water Legend
Row(
children: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 6),
Text(
"Bore Water",
style: fontTextStyle(12, Colors.black87, FontWeight.w600),
),
],
),
],
);
}
class DoublePriceBarGraph extends StatelessWidget {
final List<Map<String, dynamic>> drinkingData;
final List<Map<String, dynamic>> boreData;
const DoublePriceBarGraph({
super.key,
required this.drinkingData,
required this.boreData,
});
@override
Widget build(BuildContext context) {
final drinkMap = {for (var d in drinkingData) d["date"]: d};
final boreMap = {for (var b in boreData) b["date"]: b};
final allDates = [...drinkMap.keys, ...boreMap.keys].toSet().toList()
..sort((a, b) =>
DateTime.parse(a).compareTo(DateTime.parse(b))); // FIXED
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: allDates.map((date) {
final d = drinkMap[date];
final b = boreMap[date];
double drinkPrice = d?["price"]?.toDouble() ?? 0.0;
double borePrice = b?["price"]?.toDouble() ?? 0.0;
String day = DateTime.parse(date).day.toString();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Drinking
_buildBar(drinkPrice, Colors.blueAccent),
const SizedBox(width: 0),
// Bore
_buildBar(borePrice, Colors.orangeAccent)
],
),
const SizedBox(height: 6),
Text(day, style: fontTextStyle(12, Colors.black87, FontWeight.w600)),
],
),
);
}).toList(),
);
}
Widget _buildBar(double price, Color color) {
return Column(
children: [
Text(
"${price.toInt()}",
style: fontTextStyle(9, color, FontWeight.bold),
),
Container(
width: 20,
height: price / 15, // Adjust scaling if bars too short
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(5),
),
),
],
);
}
}
final Map<String, List<Map<String, dynamic>>> weeklyPriceData = {
"Drinking Water": [
{
"date": "2025-11-01",
"price": 1570,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1600
},
{
"date": "2025-11-02",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1660,
"avgPrice": 1610
},
{
"date": "2025-11-03",
"price": 1550,
"minPrice": 1520,
"maxPrice": 1600,
"avgPrice": 1560
},
{
"date": "2025-11-04",
"price": 1610,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-05",
"price": 1630,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-06",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1600
},
{
"date": "2025-11-07",
"price": 1600,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-08",
"price": 1640,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-09",
"price": 1620,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-10",
"price": 1575,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1600
},
{
"date": "2025-11-11",
"price": 1585,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1605
},
{
"date": "2025-11-12",
"price": 1615,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1625
},
{
"date": "2025-11-13",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1650,
"avgPrice": 1610
},
{
"date": "2025-11-14",
"price": 1645,
"minPrice": 1610,
"maxPrice": 1710,
"avgPrice": 1660
},
{
"date": "2025-11-15",
"price": 1625,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1635
},
{
"date": "2025-11-16",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1595
},
{
"date": "2025-11-17",
"price": 1570,
"minPrice": 1540,
"maxPrice": 1630,
"avgPrice": 1580
},
{
"date": "2025-11-18",
"price": 1600,
"minPrice": 1580,
"maxPrice": 1660,
"avgPrice": 1620
},
{
"date": "2025-11-19",
"price": 1630,
"minPrice": 1600,
"maxPrice": 1690,
"avgPrice": 1650
},
{
"date": "2025-11-20",
"price": 1650,
"minPrice": 1610,
"maxPrice": 1720,
"avgPrice": 1670
},
{
"date": "2025-11-21",
"price": 1610,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1630
},
{
"date": "2025-11-22",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1650,
"avgPrice": 1605
},
{
"date": "2025-11-23",
"price": 1620,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-24",
"price": 1670,
"minPrice": 1630,
"maxPrice": 1730,
"avgPrice": 1680
},
{
"date": "2025-11-25",
"price": 1690,
"minPrice": 1650,
"maxPrice": 1750,
"avgPrice": 1700
},
{
"date": "2025-11-26",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1600
},
{
"date": "2025-11-27",
"price": 1600,
"minPrice": 1570,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-28",
"price": 1615,
"minPrice": 1580,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-29",
"price": 1635,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-30",
"price": 1605,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
],
"bore Water": [
{
"date": "2025-11-01",
"price": 780,
"minPrice": 760,
"maxPrice": 810,
"avgPrice": 790
},
{
"date": "2025-11-02",
"price": 800,
"minPrice": 780,
"maxPrice": 840,
"avgPrice": 810
},
{
"date": "2025-11-03",
"price": 770,
"minPrice": 750,
"maxPrice": 800,
"avgPrice": 780
},
{
"date": "2025-11-04",
"price": 820,
"minPrice": 800,
"maxPrice": 850,
"avgPrice": 830
},
{
"date": "2025-11-05",
"price": 830,
"minPrice": 810,
"maxPrice": 860,
"avgPrice": 840
},
{
"date": "2025-11-06",
"price": 785,
"minPrice": 760,
"maxPrice": 820,
"avgPrice": 795
},
{
"date": "2025-11-07",
"price": 810,
"minPrice": 790,
"maxPrice": 840,
"avgPrice": 820
},
{
"date": "2025-11-08",
"price": 825,
"minPrice": 800,
"maxPrice": 860,
"avgPrice": 835
},
{
"date": "2025-11-09",
"price": 795,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 810
},
{
"date": "2025-11-10",
"price": 820,
"minPrice": 790,
"maxPrice": 860,
"avgPrice": 830
},
{
"date": "2025-11-11",
"price": 810,
"minPrice": 780,
"maxPrice": 850,
"avgPrice": 820
},
{
"date": "2025-11-12",
"price": 830,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-13",
"price": 785,
"minPrice": 770,
"maxPrice": 820,
"avgPrice": 795
},
{
"date": "2025-11-14",
"price": 750,
"minPrice": 730,
"maxPrice": 790,
"avgPrice": 760
},
{
"date": "2025-11-15",
"price": 770,
"minPrice": 750,
"maxPrice": 810,
"avgPrice": 780
},
{
"date": "2025-11-16",
"price": 825,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-17",
"price": 840,
"minPrice": 820,
"maxPrice": 880,
"avgPrice": 850
},
{
"date": "2025-11-18",
"price": 810,
"minPrice": 790,
"maxPrice": 840,
"avgPrice": 820
},
{
"date": "2025-11-19",
"price": 790,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 800
},
{
"date": "2025-11-20",
"price": 830,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-21",
"price": 850,
"minPrice": 820,
"maxPrice": 890,
"avgPrice": 860
},
{
"date": "2025-11-22",
"price": 780,
"minPrice": 760,
"maxPrice": 810,
"avgPrice": 790
},
{
"date": "2025-11-23",
"price": 790,
"minPrice": 770,
"maxPrice": 820,
"avgPrice": 800
},
{
"date": "2025-11-24",
"price": 835,
"minPrice": 810,
"maxPrice": 870,
"avgPrice": 845
},
{
"date": "2025-11-25",
"price": 820,
"minPrice": 800,
"maxPrice": 860,
"avgPrice": 830
},
{
"date": "2025-11-26",
"price": 760,
"minPrice": 740,
"maxPrice": 800,
"avgPrice": 780
},
{
"date": "2025-11-27",
"price": 810,
"minPrice": 780,
"maxPrice": 850,
"avgPrice": 820
},
{
"date": "2025-11-28",
"price": 795,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 805
},
{
"date": "2025-11-29",
"price": 835,
"minPrice": 810,
"maxPrice": 870,
"avgPrice": 845
},
{
"date": "2025-11-30",
"price": 820,
"minPrice": 790,
"maxPrice": 860,
"avgPrice": 830
},
]
};