Your changes

This commit is contained in:
divya abdar
2025-12-11 18:36:11 +05:30
parent 3bf27cc29d
commit 9faf983b95
36 changed files with 4677 additions and 1046 deletions

View File

@@ -2,11 +2,8 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/invoice_installment_screen.dart';
import '../providers/invoice_provider.dart';
import '../services/dio_client.dart';
import '../services/invoice_service.dart';
import 'invoice_detail_screen.dart';
class InvoiceScreen extends StatefulWidget {
const InvoiceScreen({super.key});
@@ -15,10 +12,11 @@ class InvoiceScreen extends StatefulWidget {
}
class _InvoiceScreenState extends State<InvoiceScreen> {
String searchQuery = "";
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<InvoiceProvider>(context, listen: false)
.loadInvoices(context);
@@ -29,78 +27,263 @@ class _InvoiceScreenState extends State<InvoiceScreen> {
Widget build(BuildContext context) {
final provider = Provider.of<InvoiceProvider>(context);
final width = MediaQuery.of(context).size.width;
final scale = (width / 430).clamp(0.88, 1.08);
if (provider.loading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.invoices.isEmpty) {
return const Center(
child: Text("No invoices found", style: TextStyle(fontSize: 18)));
}
// 🔍 Filter invoices based on search query
final filteredInvoices = provider.invoices.where((inv) {
final q = searchQuery.toLowerCase();
return inv['invoice_number'].toString().toLowerCase().contains(q) ||
inv['invoice_date'].toString().toLowerCase().contains(q) ||
inv['formatted_amount'].toString().toLowerCase().contains(q) ||
inv['status'].toString().toLowerCase().contains(q);
}).toList();
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: provider.invoices.length,
itemBuilder: (_, i) {
final inv = provider.invoices[i];
return Column(
children: [
// 🔍 SEARCH BAR
Container(
margin: EdgeInsets.fromLTRB(16 * scale, 16 * scale, 16 * scale, 8 * scale),
padding: EdgeInsets.symmetric(horizontal: 14 * scale),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14 * scale),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(0.08),
blurRadius: 8 * scale,
offset: Offset(0, 3 * scale),
),
],
),
child: TextField(
onChanged: (v) => setState(() => searchQuery = v),
style: TextStyle(fontSize: 14 * scale),
decoration: InputDecoration(
icon: Icon(Icons.search, size: 22 * scale),
hintText: "Search Invoice Number, Date, Amount...",
border: InputBorder.none,
),
),
),
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Invoice ${inv['invoice_number'] ?? 'N/A'}",
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
// 📄 LIST OF INVOICES
Expanded(
child: filteredInvoices.isEmpty
? Center(
child: Text(
"No invoices found",
style: TextStyle(
fontSize: 18 * scale,
fontWeight: FontWeight.w600,
),
),
)
: ListView.builder(
padding: EdgeInsets.all(16 * scale),
itemCount: filteredInvoices.length,
itemBuilder: (_, i) {
final inv = filteredInvoices[i];
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16 * scale),
),
const SizedBox(height: 6),
Text("Date: ${inv['invoice_date'] ?? 'N/A'}"),
Text("Status: ${inv['status'] ?? 'N/A'}"),
Text("Amount: ₹${inv['formatted_amount'] ?? '0'}"),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
elevation: 3,
margin: EdgeInsets.only(bottom: 14 * scale),
child: Stack(
children: [
OutlinedButton(
child: const Text("Invoice Details"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => InvoiceDetailScreen(
invoiceId: inv['invoice_id'],
Padding(
padding: EdgeInsets.all(16 * scale),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Invoice Number
Text(
"Invoice ${inv['invoice_number'] ?? 'N/A'}",
style: TextStyle(
fontSize: 20 * scale,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
);
},
SizedBox(height: 8 * scale),
/// Date + Amount
Text(
"Date: ${inv['invoice_date'] ?? 'N/A'}",
style: TextStyle(fontSize: 15 * scale),
),
Text(
"Amount: ₹${inv['formatted_amount'] ?? '0'}",
style: TextStyle(fontSize: 15 * scale),
),
SizedBox(height: 16 * scale),
/// BUTTONS ROW
Row(
children: [
Expanded(
child: GradientButton(
text: "Invoice Details",
fontSize: 15 * scale,
radius: 12 * scale,
padding: 14 * scale,
gradient: const LinearGradient(
colors: [
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
InvoiceDetailScreen(
invoiceId:
inv['invoice_id']),
),
);
},
),
),
SizedBox(width: 12 * scale),
Expanded(
child: GradientButton(
text: "Installments",
fontSize: 15 * scale,
radius: 12 * scale,
padding: 14 * scale,
gradient: const LinearGradient(
colors: [
Color(0xFF43A047),
Color(0xFF81C784),
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
InvoiceInstallmentScreen(
invoiceId:
inv['invoice_id']),
),
);
},
),
),
],
),
],
),
),
OutlinedButton(
child: const Text("Installments"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => InvoiceInstallmentScreen(
invoiceId: inv['invoice_id'],
),
/// STATUS BADGE
Positioned(
right: 12 * scale,
top: 12 * scale,
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10 * scale,
vertical: 6 * scale,
),
decoration: BoxDecoration(
color: _getStatusColor(inv['status']),
borderRadius: BorderRadius.circular(12 * scale),
),
child: Text(
inv['status'] ?? 'N/A',
style: TextStyle(
color: Colors.white,
fontSize: 12 * scale,
fontWeight: FontWeight.bold,
),
);
},
),
),
),
],
),
],
),
);
},
),
);
},
),
],
);
}
}
/// Status Color Helper
Color _getStatusColor(String? status) {
switch (status?.toLowerCase()) {
case 'pending':
return Colors.orange;
case 'in transit':
return Colors.blue;
case 'overdue':
return Colors.redAccent;
case 'paid':
return Colors.green;
default:
return Colors.grey;
}
}
/// -------------------------------------------------------
/// RESPONSIVE GRADIENT BUTTON
/// -------------------------------------------------------
class GradientButton extends StatelessWidget {
final String text;
final Gradient gradient;
final VoidCallback onTap;
final double fontSize;
final double padding;
final double radius;
const GradientButton({
super.key,
required this.text,
required this.gradient,
required this.onTap,
required this.fontSize,
required this.padding,
required this.radius,
});
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(radius),
onTap: onTap,
child: Ink(
decoration: BoxDecoration(
gradient: gradient,
borderRadius: BorderRadius.circular(radius),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: padding),
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
fontWeight: FontWeight.w600,
),
),
),
),
);
}
}