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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
},
]
};