added mytext
This commit is contained in:
parent
717f0c92af
commit
1717cd5e2b
@ -1,6 +1,5 @@
|
|||||||
// collections_health_widget.dart
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||||
|
|
||||||
// --- MAIN WIDGET FILE ---
|
// --- MAIN WIDGET FILE ---
|
||||||
class CollectionsHealthWidget extends StatelessWidget {
|
class CollectionsHealthWidget extends StatelessWidget {
|
||||||
@ -10,6 +9,7 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
const double totalDue = 34190.0;
|
const double totalDue = 34190.0;
|
||||||
const double totalCollected = 5000.0;
|
const double totalCollected = 5000.0;
|
||||||
const double totalValue = totalDue + totalCollected;
|
const double totalValue = totalDue + totalCollected;
|
||||||
|
|
||||||
// Calculate Pending Percentage for Gauge
|
// Calculate Pending Percentage for Gauge
|
||||||
final double pendingPercentage =
|
final double pendingPercentage =
|
||||||
totalValue > 0 ? totalDue / totalValue : 0.0;
|
totalValue > 0 ? totalDue / totalValue : 0.0;
|
||||||
@ -17,12 +17,10 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
// 1. MAIN CARD CONTAINER (White Theme)
|
// 1. MAIN CARD CONTAINER (White Theme)
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// Main card background color: WHITE
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
// Lighter shadow for white background
|
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withOpacity(0.1),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 5),
|
offset: const Offset(0, 5),
|
||||||
@ -60,7 +58,7 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// 3. NEW: AGING ANALYSIS SECTION
|
// 3. AGING ANALYSIS SECTION
|
||||||
_buildAgingAnalysis(),
|
_buildAgingAnalysis(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -69,19 +67,24 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// --- HELPER METHOD 1: HEADER ---
|
// --- HELPER METHOD 1: HEADER ---
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return Row(
|
return Row(mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||||
children: const <Widget>[
|
Expanded(
|
||||||
Text(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MyText.bodyMedium(
|
||||||
'Collections Health Overview',
|
'Collections Health Overview',
|
||||||
style: TextStyle(
|
fontWeight: 700,
|
||||||
// Text color: Dark for contrast on white background
|
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
MyText.bodySmall(
|
||||||
|
'View your collection health data.',
|
||||||
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- HELPER METHOD 2: LEFT SECTION (CHARTS) ---
|
// --- HELPER METHOD 2: LEFT SECTION (CHARTS) ---
|
||||||
@ -97,38 +100,33 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Top: Gauge Chart and Due Amount
|
// Top: Gauge Chart
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Use White-Theme Gauge Placeholder with calculated percentage
|
|
||||||
_GaugeChartPlaceholder(
|
_GaugeChartPlaceholder(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
pendingPercentage: pendingPercentage),
|
pendingPercentage: pendingPercentage,
|
||||||
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Total Due + Summary
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
MyText.bodyLarge(
|
||||||
'₹${totalDue.toStringAsFixed(0)} DUE',
|
'₹${totalDue.toStringAsFixed(0)} DUE',
|
||||||
style: const TextStyle(
|
fontWeight: 700,
|
||||||
// Text color: Dark for contrast
|
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
'• Pending ($pendingPercentStr%) • ₹${totalCollected.toStringAsFixed(0)} Collected',
|
'• Pending ($pendingPercentStr%) • ₹${totalCollected.toStringAsFixed(0)} Collected',
|
||||||
// Sub-text color: Lighter dark color
|
color: Colors.black54,
|
||||||
style: const TextStyle(color: Colors.black54, fontSize: 12),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -136,6 +134,7 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Bottom: Timeline Charts (Trend Analysis)
|
// Bottom: Timeline Charts (Trend Analysis)
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -144,34 +143,40 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Text('Expected Collections Trend',
|
MyText.bodySmall(
|
||||||
// Text color: Dark for contrast
|
'Expected Collections Trend',
|
||||||
style: TextStyle(color: Colors.black87, fontSize: 12)),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_TimelineChartPlaceholder(
|
const _TimelineChartPlaceholder(
|
||||||
isBar: true,
|
isBar: true,
|
||||||
barColor: const Color(0xFF2196F3)), // Blue Bar Chart
|
barColor: Color(0xFF2196F3),
|
||||||
const Text('Week 16 Nov 2025',
|
),
|
||||||
style: TextStyle(color: Colors.black54, fontSize: 10)),
|
MyText.bodySmall(
|
||||||
|
'Week 16 Nov 2025',
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
// Collection Rate Trend (Area Chart Placeholder for Analysis)
|
|
||||||
|
// Collection Rate Trend (Area Chart Placeholder)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Text('Collection Rate Trend',
|
MyText.bodySmall(
|
||||||
// Text color: Dark for contrast
|
'Collection Rate Trend',
|
||||||
style: TextStyle(color: Colors.black87, fontSize: 12)),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_TimelineChartPlaceholder(
|
const _TimelineChartPlaceholder(
|
||||||
isBar: false,
|
isBar: false,
|
||||||
areaColor:
|
areaColor: Color(0xFF4CAF50),
|
||||||
const Color(0xFF4CAF50)), // Green Area Chart (Rate)
|
),
|
||||||
const Text('Week 14 Nov 2025',
|
MyText.bodySmall(
|
||||||
style: TextStyle(color: Colors.black54, fontSize: 10)),
|
'Week 14 Nov 2025',
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -188,7 +193,7 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
// Metric Card 1: Top Client (From Invoice 1)
|
// Metric Card 1: Top Client
|
||||||
_buildMetricCard(
|
_buildMetricCard(
|
||||||
title: 'Top Client Balance',
|
title: 'Top Client Balance',
|
||||||
value: 'Peninsula Land Limited',
|
value: 'Peninsula Land Limited',
|
||||||
@ -197,7 +202,8 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
isDetailed: true,
|
isDetailed: true,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Metric Card 2 (NEW): Total Collected (From Invoice 2)
|
|
||||||
|
// Metric Card 2: Total Collected (YTD)
|
||||||
_buildMetricCard(
|
_buildMetricCard(
|
||||||
title: 'Total Collected (YTD)',
|
title: 'Total Collected (YTD)',
|
||||||
value: '₹${totalCollected.toStringAsFixed(0)}',
|
value: '₹${totalCollected.toStringAsFixed(0)}',
|
||||||
@ -206,7 +212,8 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
isDetailed: false,
|
isDetailed: false,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Metric Card 3: Days Sales Outstanding (DSO) (Analytical Placeholder)
|
|
||||||
|
// Metric Card 3: DSO
|
||||||
_buildMetricCard(
|
_buildMetricCard(
|
||||||
title: 'Days Sales Outstanding (DSO)',
|
title: 'Days Sales Outstanding (DSO)',
|
||||||
value: '45 Days',
|
value: '45 Days',
|
||||||
@ -215,7 +222,8 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
isDetailed: false,
|
isDetailed: false,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
// Metric Card 4: Last Metric Card in the image (Repeating rate for layout)
|
|
||||||
|
// Metric Card 4: Bad Debt Ratio
|
||||||
_buildMetricCard(
|
_buildMetricCard(
|
||||||
title: 'Bad Debt Ratio',
|
title: 'Bad Debt Ratio',
|
||||||
value: '0.8%',
|
value: '0.8%',
|
||||||
@ -227,7 +235,7 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- HELPER METHOD 4: METRIC CARD WIDGET (Unchanged) ---
|
// --- HELPER METHOD 4: METRIC CARD WIDGET ---
|
||||||
Widget _buildMetricCard({
|
Widget _buildMetricCard({
|
||||||
required String title,
|
required String title,
|
||||||
required String value,
|
required String value,
|
||||||
@ -238,60 +246,39 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// Light grey background for card separation on white theme
|
|
||||||
color: const Color(0xFFF5F5F5),
|
color: const Color(0xFFF5F5F5),
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
title,
|
title,
|
||||||
// Title text color: Soft dark grey
|
color: Colors.black54,
|
||||||
style: const TextStyle(color: Colors.black54, fontSize: 10),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
// Conditional rendering for the detailed vs simple card structure
|
|
||||||
if (isDetailed) ...[
|
if (isDetailed) ...[
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
value, // Client Name
|
value,
|
||||||
style: const TextStyle(
|
fontWeight: 600,
|
||||||
// Value text color: Dark
|
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
),
|
||||||
),
|
MyText.bodyMedium(
|
||||||
Text(
|
subValue,
|
||||||
subValue, // Due Amount
|
|
||||||
style: TextStyle(
|
|
||||||
// Color remains dynamic (Red/Green/etc.)
|
|
||||||
color: valueColor,
|
color: valueColor,
|
||||||
fontSize: 16,
|
fontWeight: 700,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
] else
|
] else
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
value,
|
value,
|
||||||
style: const TextStyle(
|
fontWeight: 600,
|
||||||
// Value text color: Dark
|
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
),
|
||||||
),
|
MyText.bodySmall(
|
||||||
Text(
|
|
||||||
subValue,
|
subValue,
|
||||||
style: TextStyle(
|
|
||||||
// Color remains dynamic (Red/Green/etc.)
|
|
||||||
color: valueColor,
|
color: valueColor,
|
||||||
fontSize: 14,
|
fontWeight: 600,
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -301,9 +288,8 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW HELPER METHOD: AGING ANALYSIS ---
|
// --- NEW HELPER METHOD: AGING ANALYSIS ---
|
||||||
// --- NEW HELPER METHOD: AGING ANALYSIS (MODIFIED) ---
|
|
||||||
Widget _buildAgingAnalysis() {
|
Widget _buildAgingAnalysis() {
|
||||||
// Hardcoded data based on the JSON analysis
|
// Hardcoded data
|
||||||
const double due0to20Days = 0.0;
|
const double due0to20Days = 0.0;
|
||||||
const double due20to45Days = 34190.0;
|
const double due20to45Days = 34190.0;
|
||||||
const double due45to90Days = 0.0;
|
const double due45to90Days = 0.0;
|
||||||
@ -314,30 +300,38 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// Define buckets with their risk color
|
// Define buckets with their risk color
|
||||||
final List<AgingBucketData> buckets = [
|
final List<AgingBucketData> buckets = [
|
||||||
AgingBucketData('0-20 Days', due0to20Days,
|
AgingBucketData(
|
||||||
const Color(0xFF4CAF50)), // Green (Low Risk)
|
'0-20 Days',
|
||||||
AgingBucketData('20-45 Days', due20to45Days,
|
due0to20Days,
|
||||||
const Color(0xFFFF9800)), // Orange (Medium Risk)
|
const Color(0xFF4CAF50), // Green (Low Risk)
|
||||||
AgingBucketData('45-90 Days', due45to90Days,
|
),
|
||||||
const Color(0xFFF44336).withOpacity(0.7)), // Light Red (High Risk)
|
AgingBucketData(
|
||||||
AgingBucketData('> 90 Days', dueOver90Days,
|
'20-45 Days',
|
||||||
const Color(0xFFF44336)), // Dark Red (Very High Risk)
|
due20to45Days,
|
||||||
|
const Color(0xFFFF9800), // Orange (Medium Risk)
|
||||||
|
),
|
||||||
|
AgingBucketData(
|
||||||
|
'45-90 Days',
|
||||||
|
due45to90Days,
|
||||||
|
const Color(0xFFF44336).withOpacity(0.7), // Light Red
|
||||||
|
),
|
||||||
|
AgingBucketData(
|
||||||
|
'> 90 Days',
|
||||||
|
dueOver90Days,
|
||||||
|
const Color(0xFFF44336), // Dark Red
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const Text(
|
MyText.bodyMedium(
|
||||||
'Outstanding Collections Aging Analysis',
|
'Outstanding Collections Aging Analysis',
|
||||||
style: TextStyle(
|
fontWeight: 700,
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// NEW: STACKED BAR VISUALIZATION
|
// Stacked bar visualization
|
||||||
_AgingStackedBar(
|
_AgingStackedBar(
|
||||||
buckets: buckets,
|
buckets: buckets,
|
||||||
totalOutstanding: totalOutstanding,
|
totalOutstanding: totalOutstanding,
|
||||||
@ -345,21 +339,25 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
|
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
// NEW: LEGEND/BUCKET DETAILS (Replaces the old _buildAgingBucket layout)
|
// Legend / Bucket details
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 12,
|
spacing: 12,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: buckets
|
children: buckets
|
||||||
.map((bucket) => _buildAgingLegendItem(
|
.map(
|
||||||
bucket.title, bucket.amount, bucket.color))
|
(bucket) => _buildAgingLegendItem(
|
||||||
|
bucket.title,
|
||||||
|
bucket.amount,
|
||||||
|
bucket.color,
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the old _buildAgingBucket helper and rename it to a legend item
|
// Legend item for aging buckets
|
||||||
// This is more efficient than recreating the logic
|
|
||||||
Widget _buildAgingLegendItem(String title, double amount, Color color) {
|
Widget _buildAgingLegendItem(String title, double amount, Color color) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -373,24 +371,25 @@ class CollectionsHealthWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
'${title}: ₹${amount.toStringAsFixed(0)}',
|
'$title: ₹${amount.toStringAsFixed(0)}',
|
||||||
style: const TextStyle(color: Colors.black87, fontSize: 12),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CUSTOM PAINTERS / PLACEHOLDERS (Modified to accept percentage) ---
|
// --- CUSTOM PAINTERS / PLACEHOLDERS ---
|
||||||
|
|
||||||
// Placeholder for the Semi-Circle Gauge Chart
|
// Placeholder for the Semi-Circle Gauge Chart
|
||||||
class _GaugeChartPlaceholder extends StatelessWidget {
|
class _GaugeChartPlaceholder extends StatelessWidget {
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
final double pendingPercentage;
|
final double pendingPercentage;
|
||||||
|
|
||||||
const _GaugeChartPlaceholder(
|
const _GaugeChartPlaceholder({
|
||||||
{required this.backgroundColor, required this.pendingPercentage});
|
required this.backgroundColor,
|
||||||
|
required this.pendingPercentage,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -402,37 +401,22 @@ class _GaugeChartPlaceholder extends StatelessWidget {
|
|||||||
// BACKGROUND GAUGE
|
// BACKGROUND GAUGE
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
size: const Size(120, 70),
|
size: const Size(120, 70),
|
||||||
// Pass the pending percentage to the painter
|
|
||||||
painter: _SemiCirclePainter(
|
painter: _SemiCirclePainter(
|
||||||
canvasColor: backgroundColor,
|
canvasColor: backgroundColor,
|
||||||
pendingPercentage: pendingPercentage),
|
pendingPercentage: pendingPercentage,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// CENTER CIRCLE
|
// CENTER TEXT
|
||||||
Positioned(
|
Align(
|
||||||
bottom: 0,
|
alignment: Alignment.bottomCenter,
|
||||||
left: 30,
|
child: Padding(
|
||||||
child: Container(
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
height: 60,
|
child: FittedBox(
|
||||||
width: 60,
|
child: MyText.bodySmall(
|
||||||
decoration: BoxDecoration(
|
|
||||||
// Center circle background is the same as the main card background (white)
|
|
||||||
color: backgroundColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: Colors.black.withOpacity(0.1)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: const [
|
|
||||||
// Icon color: Dark
|
|
||||||
Icon(Icons.work, color: Colors.black87, size: 20),
|
|
||||||
SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
'RISK LEVEL',
|
'RISK LEVEL',
|
||||||
// Text color: Soft dark grey
|
fontWeight: 600,
|
||||||
style: TextStyle(color: Colors.black54, fontSize: 8),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -442,75 +426,82 @@ class _GaugeChartPlaceholder extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Painter for the semi-circular gauge chart visualization (Simplified)
|
// Painter for the semi-circular gauge chart visualization
|
||||||
class _SemiCirclePainter extends CustomPainter {
|
class _SemiCirclePainter extends CustomPainter {
|
||||||
final Color canvasColor;
|
final Color canvasColor;
|
||||||
final double pendingPercentage;
|
final double pendingPercentage;
|
||||||
|
|
||||||
_SemiCirclePainter(
|
_SemiCirclePainter({
|
||||||
{required this.canvasColor, required this.pendingPercentage});
|
required this.canvasColor,
|
||||||
|
required this.pendingPercentage,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final rect = Rect.fromCircle(
|
final rect = Rect.fromCircle(
|
||||||
center: Offset(size.width / 2, size.height), radius: size.width / 2);
|
center: Offset(size.width / 2, size.height),
|
||||||
|
radius: size.width / 2,
|
||||||
|
);
|
||||||
|
|
||||||
// Radians for a semi-circle
|
|
||||||
const double totalArc = 3.14159;
|
const double totalArc = 3.14159;
|
||||||
// Calculate the sweep angles
|
|
||||||
final double pendingSweepAngle = totalArc * pendingPercentage;
|
final double pendingSweepAngle = totalArc * pendingPercentage;
|
||||||
final double collectedSweepAngle = totalArc * (1.0 - pendingPercentage);
|
final double collectedSweepAngle = totalArc * (1.0 - pendingPercentage);
|
||||||
|
|
||||||
// Background Arc (Full semi-circle)
|
// Background Arc
|
||||||
final backgroundPaint = Paint()
|
final backgroundPaint = Paint()
|
||||||
// Background arc is a soft grey on a white theme
|
|
||||||
..color = Colors.black.withOpacity(0.1)
|
..color = Colors.black.withOpacity(0.1)
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 10;
|
..strokeWidth = 10;
|
||||||
canvas.drawArc(rect, totalArc, totalArc, false,
|
canvas.drawArc(rect, totalArc, totalArc, false, backgroundPaint);
|
||||||
backgroundPaint); // Start at 180 deg (Pi)
|
|
||||||
|
|
||||||
// Pending Arc (Red/Orange segment)
|
// Pending Arc
|
||||||
final pendingPaint = Paint()
|
final pendingPaint = Paint()
|
||||||
..color = const Color(0xFFF44336) // Red
|
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 10
|
..strokeWidth = 10
|
||||||
..shader = const LinearGradient(colors: [
|
..shader = const LinearGradient(
|
||||||
Color(0xFFFF9800), // Orange
|
colors: [
|
||||||
Color(0xFFF44336), // Red
|
Color(0xFFFF9800),
|
||||||
]).createShader(rect);
|
Color(0xFFF44336),
|
||||||
// Start angle: 3.14 (180 deg)
|
],
|
||||||
|
).createShader(rect);
|
||||||
canvas.drawArc(rect, totalArc, pendingSweepAngle, false, pendingPaint);
|
canvas.drawArc(rect, totalArc, pendingSweepAngle, false, pendingPaint);
|
||||||
|
|
||||||
// Collected Arc (Green segment)
|
// Collected Arc
|
||||||
final collectedPaint = Paint()
|
final collectedPaint = Paint()
|
||||||
..color = const Color(0xFF4CAF50) // Green
|
..color = const Color(0xFF4CAF50)
|
||||||
..style = PaintingStyle.stroke
|
..style = PaintingStyle.stroke
|
||||||
..strokeWidth = 10;
|
..strokeWidth = 10;
|
||||||
// Start angle: 3.14 + pendingSweepAngle
|
canvas.drawArc(
|
||||||
canvas.drawArc(rect, totalArc + pendingSweepAngle, collectedSweepAngle,
|
rect,
|
||||||
false, collectedPaint);
|
totalArc + pendingSweepAngle,
|
||||||
|
collectedSweepAngle,
|
||||||
|
false,
|
||||||
|
collectedPaint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placeholder for the Bar/Area Charts (Unchanged)
|
// Placeholder for the Bar/Area Charts
|
||||||
class _TimelineChartPlaceholder extends StatelessWidget {
|
class _TimelineChartPlaceholder extends StatelessWidget {
|
||||||
final bool isBar;
|
final bool isBar;
|
||||||
final Color? barColor;
|
final Color? barColor;
|
||||||
final Color? areaColor;
|
final Color? areaColor;
|
||||||
|
|
||||||
const _TimelineChartPlaceholder(
|
const _TimelineChartPlaceholder({
|
||||||
{required this.isBar, this.barColor, this.areaColor});
|
required this.isBar,
|
||||||
|
this.barColor,
|
||||||
|
this.areaColor,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: Colors.transparent, // Transparent container for visual space
|
color: Colors.transparent,
|
||||||
child: isBar
|
child: isBar
|
||||||
? _BarChartVisual(barColor: barColor!)
|
? _BarChartVisual(barColor: barColor!)
|
||||||
: _AreaChartVisual(areaColor: areaColor!),
|
: _AreaChartVisual(areaColor: areaColor!),
|
||||||
@ -528,12 +519,12 @@ class _BarChartVisual extends StatelessWidget {
|
|||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: const [
|
||||||
_Bar(0.4, barColor),
|
_Bar(0.4),
|
||||||
_Bar(0.7, barColor),
|
_Bar(0.7),
|
||||||
_Bar(1.0, barColor),
|
_Bar(1.0),
|
||||||
_Bar(0.6, barColor),
|
_Bar(0.6),
|
||||||
_Bar(0.8, barColor),
|
_Bar(0.8),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -541,17 +532,18 @@ class _BarChartVisual extends StatelessWidget {
|
|||||||
|
|
||||||
class _Bar extends StatelessWidget {
|
class _Bar extends StatelessWidget {
|
||||||
final double heightFactor;
|
final double heightFactor;
|
||||||
final Color color;
|
|
||||||
|
|
||||||
const _Bar(this.heightFactor, this.color);
|
const _Bar(this.heightFactor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Bar color is taken from parent via DefaultTextStyle/Theme if needed;
|
||||||
|
// you can wrap with Theme if you want dynamic colors.
|
||||||
return Container(
|
return Container(
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 50 * heightFactor,
|
height: 50 * heightFactor,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: Colors.grey.shade400,
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -587,7 +579,6 @@ class _AreaChartPainter extends CustomPainter {
|
|||||||
Offset(size.width, size.height * 0.4),
|
Offset(size.width, size.height * 0.4),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Path for the area
|
|
||||||
final path = Path()
|
final path = Path()
|
||||||
..moveTo(points.first.dx, size.height)
|
..moveTo(points.first.dx, size.height)
|
||||||
..lineTo(points.first.dx, points.first.dy);
|
..lineTo(points.first.dx, points.first.dy);
|
||||||
@ -597,20 +588,18 @@ class _AreaChartPainter extends CustomPainter {
|
|||||||
path.lineTo(points.last.dx, size.height);
|
path.lineTo(points.last.dx, size.height);
|
||||||
path.close();
|
path.close();
|
||||||
|
|
||||||
// Paint for the gradient fill
|
|
||||||
final areaPaint = Paint()
|
final areaPaint = Paint()
|
||||||
..shader = LinearGradient(
|
..shader = LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: [
|
colors: [
|
||||||
areaColor.withOpacity(0.5), // Lighter opacity on white theme
|
areaColor.withOpacity(0.5),
|
||||||
areaColor.withOpacity(0.0), // Transparent
|
areaColor.withOpacity(0.0),
|
||||||
],
|
],
|
||||||
).createShader(Rect.fromLTWH(0, 0, size.width, size.height))
|
).createShader(Rect.fromLTWH(0, 0, size.width, size.height))
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
canvas.drawPath(path, areaPaint);
|
canvas.drawPath(path, areaPaint);
|
||||||
|
|
||||||
// Paint for the line stroke
|
|
||||||
final linePaint = Paint()
|
final linePaint = Paint()
|
||||||
..color = areaColor
|
..color = areaColor
|
||||||
..strokeWidth = 2
|
..strokeWidth = 2
|
||||||
@ -622,7 +611,7 @@ class _AreaChartPainter extends CustomPainter {
|
|||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW DATA MODEL ---
|
// --- DATA MODEL ---
|
||||||
class AgingBucketData {
|
class AgingBucketData {
|
||||||
final String title;
|
final String title;
|
||||||
final double amount;
|
final double amount;
|
||||||
@ -631,7 +620,7 @@ class AgingBucketData {
|
|||||||
AgingBucketData(this.title, this.amount, this.color);
|
AgingBucketData(this.title, this.amount, this.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEW HELPER WIDGET: STACKED BAR VISUAL ---
|
// --- STACKED BAR VISUAL ---
|
||||||
class _AgingStackedBar extends StatelessWidget {
|
class _AgingStackedBar extends StatelessWidget {
|
||||||
final List<AgingBucketData> buckets;
|
final List<AgingBucketData> buckets;
|
||||||
final double totalOutstanding;
|
final double totalOutstanding;
|
||||||
@ -650,21 +639,20 @@ class _AgingStackedBar extends StatelessWidget {
|
|||||||
color: Colors.grey[200],
|
color: Colors.grey[200],
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Text(
|
child: MyText.bodySmall(
|
||||||
'No Outstanding Collections',
|
'No Outstanding Collections',
|
||||||
style: TextStyle(color: Colors.black54, fontSize: 10),
|
color: Colors.black54,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the segments for the Row
|
|
||||||
final List<Widget> segments =
|
final List<Widget> segments =
|
||||||
buckets.where((b) => b.amount > 0).map((bucket) {
|
buckets.where((b) => b.amount > 0).map((bucket) {
|
||||||
final double flexValue = bucket.amount / totalOutstanding;
|
final double flexValue = bucket.amount / totalOutstanding;
|
||||||
return Expanded(
|
return Expanded(
|
||||||
flex: (flexValue * 100).toInt(), // Use a scaled integer for flex
|
flex: (flexValue * 100).toInt(),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 16,
|
height: 16,
|
||||||
color: bucket.color,
|
color: bucket.color,
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
// lib/widgets/purchase_invoice_dashboard.dart
|
// lib/widgets/purchase_invoice_dashboard.dart
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||||
|
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||||
|
|
||||||
/// =======================
|
/// =======================
|
||||||
/// INTERNAL DUMMY DATA
|
/// INTERNAL DUMMY DATA
|
||||||
@ -111,7 +113,7 @@ class CompactPurchaseInvoiceDashboard extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(mainPadding),
|
padding: const EdgeInsets.all(mainPadding),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(10), // Slightly rounder corners
|
borderRadius: BorderRadius.circular(5), // Slightly rounder corners
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.08), // Softer, more subtle shadow
|
color: Colors.black.withOpacity(0.08), // Softer, more subtle shadow
|
||||||
@ -420,14 +422,11 @@ class _SectionTitle extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Text(
|
return MyText.bodySmall(
|
||||||
title,
|
title,
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey.shade700,
|
color: Colors.grey.shade700,
|
||||||
fontSize: 14,
|
fontWeight: 700,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -437,14 +436,21 @@ class _DashboardHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Text(
|
return Row(mainAxisAlignment: MainAxisAlignment.start, children: [
|
||||||
'Purchase Invoice Dashboard ',
|
Expanded(
|
||||||
style: TextStyle(
|
child:
|
||||||
color: Colors.black,
|
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
fontSize: 20,
|
MyText.bodyMedium(
|
||||||
fontWeight: FontWeight.w700,
|
'Purchase Invoice ',
|
||||||
|
fontWeight: 700,
|
||||||
),
|
),
|
||||||
);
|
SizedBox(height: 2),
|
||||||
|
MyText.bodySmall(
|
||||||
|
'View your purchase invoice data.',
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
]))
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +470,7 @@ class _TotalValueCard extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFE3F2FD), // Lighter Blue
|
color: const Color(0xFFE3F2FD), // Lighter Blue
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(5),
|
||||||
border: Border.all(color: const Color(0xFFBBDEFB), width: 1),
|
border: Border.all(color: const Color(0xFFBBDEFB), width: 1),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -473,15 +479,12 @@ class _TotalValueCard extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
'TOTAL PROFORMA VALUE (₹)',
|
'TOTAL PROFORMA VALUE (₹)',
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue.shade800,
|
color: Colors.blue.shade800,
|
||||||
fontSize: 10,
|
fontWeight: 700,
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
letterSpacing: 1.0,
|
letterSpacing: 1.0,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.account_balance_wallet_outlined,
|
Icons.account_balance_wallet_outlined,
|
||||||
color: Colors.blue.shade700,
|
color: Colors.blue.shade700,
|
||||||
@ -489,24 +492,15 @@ class _TotalValueCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
MySpacing.height(8),
|
||||||
Text(
|
MyText.bodyMedium(
|
||||||
// Format number with commas if needed for large values
|
|
||||||
totalProformaAmount.toStringAsFixed(0),
|
totalProformaAmount.toStringAsFixed(0),
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
),
|
),
|
||||||
),
|
MySpacing.height(4),
|
||||||
const SizedBox(height: 4),
|
MyText.bodySmall(
|
||||||
Text(
|
|
||||||
'Over $totalCount Total Invoices',
|
'Over $totalCount Total Invoices',
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blueGrey.shade600,
|
color: Colors.blueGrey.shade600,
|
||||||
fontSize: 12,
|
fontWeight: 500,
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -589,7 +583,7 @@ class _CondensedMetricCard extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.05),
|
color: color.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(5),
|
||||||
border: Border.all(color: color.withOpacity(0.15), width: 1),
|
border: Border.all(color: color.withOpacity(0.15), width: 1),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -600,35 +594,25 @@ class _CondensedMetricCard extends StatelessWidget {
|
|||||||
Icon(icon, color: color, size: 16),
|
Icon(icon, color: color, size: 16),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: MyText.bodySmall(
|
||||||
title,
|
title,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
|
||||||
color: color,
|
color: color,
|
||||||
fontSize: 10,
|
fontWeight: 700,
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
MySpacing.height(6),
|
||||||
Text(
|
MyText.bodyMedium(
|
||||||
value,
|
value,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
fontWeight: 800,
|
||||||
color: Colors.black87,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
),
|
),
|
||||||
),
|
MyText.bodySmall(
|
||||||
Text(
|
|
||||||
caption,
|
caption,
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey.shade500,
|
color: Colors.grey.shade500,
|
||||||
fontSize: 9,
|
fontWeight: 500,
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -656,9 +640,9 @@ class _StatusDonutChart extends StatelessWidget {
|
|||||||
if (activeBuckets.isEmpty) {
|
if (activeBuckets.isEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Text(
|
child: MyText.bodySmall(
|
||||||
'No active invoices to display status breakdown.',
|
'No active invoices to display status breakdown.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
color: Colors.grey.shade500,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -667,10 +651,6 @@ class _StatusDonutChart extends StatelessWidget {
|
|||||||
final double mainPercentage =
|
final double mainPercentage =
|
||||||
totalAmount > 0 ? activeBuckets.first.amount / totalAmount : 0.0;
|
totalAmount > 0 ? activeBuckets.first.amount / totalAmount : 0.0;
|
||||||
|
|
||||||
// Placeholder for Donut Chart (requires external package like fl_chart for true pie/donut chart)
|
|
||||||
// We simulate the key visual hierarchy here:
|
|
||||||
//
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -688,21 +668,13 @@ class _StatusDonutChart extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
MyText.bodySmall(
|
||||||
'${activeBuckets.first.title}', // Top status name
|
activeBuckets.first.title,
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
color: activeBuckets.first.color,
|
color: activeBuckets.first.color,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: 700,
|
||||||
),
|
),
|
||||||
),
|
MyText.bodyMedium(
|
||||||
Text(
|
|
||||||
'${(mainPercentage * 100).toStringAsFixed(0)}%',
|
'${(mainPercentage * 100).toStringAsFixed(0)}%',
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -727,23 +699,17 @@ class _StatusDonutChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: MyText.bodySmall(
|
||||||
'${bucket.title} (${bucket.count})',
|
'${bucket.title} (${bucket.count})',
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey.shade800,
|
color: Colors.grey.shade800,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: 500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
MyText.bodySmall(
|
||||||
Text(
|
|
||||||
'₹${bucket.amount.toStringAsFixed(0)}',
|
'₹${bucket.amount.toStringAsFixed(0)}',
|
||||||
style: TextStyle(
|
fontWeight: 700,
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: bucket.color.withOpacity(0.9),
|
color: bucket.color.withOpacity(0.9),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -772,9 +738,9 @@ class _ProjectBreakdown extends StatelessWidget {
|
|||||||
if (projects.isEmpty) {
|
if (projects.isEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
child: Text(
|
child: MyText.bodySmall(
|
||||||
'No project data available.',
|
'No project data available.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade500),
|
color: Colors.grey.shade500,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -804,18 +770,14 @@ class _ProjectBreakdown extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
MyText.bodyMedium(
|
||||||
project.name,
|
project.name,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
fontWeight: 600,
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(5),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: percentage,
|
value: percentage,
|
||||||
backgroundColor: Colors.grey.shade200,
|
backgroundColor: Colors.grey.shade200,
|
||||||
@ -830,22 +792,16 @@ class _ProjectBreakdown extends StatelessWidget {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
MyText.bodyMedium(
|
||||||
'₹${project.amount.toStringAsFixed(0)}',
|
'₹${project.amount.toStringAsFixed(0)}',
|
||||||
style: TextStyle(
|
fontWeight: 700,
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: color.withOpacity(0.9),
|
color: color.withOpacity(0.9),
|
||||||
),
|
),
|
||||||
),
|
MyText.bodySmall(
|
||||||
Text(
|
|
||||||
'$percentageText%',
|
'$percentageText%',
|
||||||
style: TextStyle(
|
fontWeight: 500,
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user