227 lines
6.4 KiB
Dart
227 lines
6.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../services/dio_client.dart';
|
|
import '../services/invoice_service.dart';
|
|
|
|
class InvoiceInstallmentScreen extends StatefulWidget {
|
|
final int invoiceId;
|
|
|
|
const InvoiceInstallmentScreen({
|
|
super.key,
|
|
required this.invoiceId,
|
|
});
|
|
|
|
@override
|
|
State<InvoiceInstallmentScreen> createState() =>
|
|
_InvoiceInstallmentScreenState();
|
|
}
|
|
|
|
class _InvoiceInstallmentScreenState extends State<InvoiceInstallmentScreen> {
|
|
bool loading = true;
|
|
List installments = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
load();
|
|
}
|
|
|
|
Future<void> load() async {
|
|
final service = InvoiceService(DioClient.getInstance(context));
|
|
final res = await service.getInstallments(widget.invoiceId);
|
|
|
|
if (res['success'] == true) {
|
|
installments = res['installments'] ?? [];
|
|
}
|
|
|
|
loading = false;
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.grey.shade100,
|
|
appBar: AppBar(
|
|
title: const Text("Installments"),
|
|
elevation: 1,
|
|
),
|
|
body: loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: installments.isEmpty
|
|
? _buildEmptyState()
|
|
: ListView.builder(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: width * 0.04,
|
|
vertical: 16,
|
|
),
|
|
itemCount: installments.length,
|
|
itemBuilder: (_, i) {
|
|
return InstallmentCard(inst: installments[i]);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState() {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.receipt_long,
|
|
size: 70, color: Colors.grey.shade400),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
"No Installments Created",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class InstallmentCard extends StatelessWidget {
|
|
final Map inst;
|
|
|
|
const InstallmentCard({super.key, required this.inst});
|
|
|
|
String getString(key) => inst[key]?.toString() ?? "N/A";
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final width = MediaQuery.of(context).size.width;
|
|
final isTablet = width > 600;
|
|
|
|
final padding = isTablet ? 28.0 : 20.0;
|
|
final amountSize = isTablet ? 30.0 : 26.0;
|
|
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 18),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(18),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(padding),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Amount + Payment Method Row
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"₹${getString('amount')}",
|
|
style: TextStyle(
|
|
fontSize: amountSize,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 0.2,
|
|
),
|
|
),
|
|
|
|
// Payment Chip
|
|
Container(
|
|
padding: EdgeInsets.symmetric(
|
|
vertical: isTablet ? 8 : 6,
|
|
horizontal: isTablet ? 16 : 12,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(50),
|
|
),
|
|
child: Text(
|
|
getString('payment_method'),
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade700,
|
|
fontSize: isTablet ? 15 : 13.5,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: isTablet ? 24 : 18),
|
|
|
|
// Responsive Info Rows
|
|
buildInfoRow(
|
|
Icons.calendar_month,
|
|
"Date",
|
|
getString("installment_date"),
|
|
isTablet
|
|
),
|
|
SizedBox(height: isTablet ? 14 : 10),
|
|
buildInfoRow(
|
|
Icons.confirmation_number,
|
|
"Reference",
|
|
getString("reference_no"),
|
|
isTablet
|
|
),
|
|
|
|
SizedBox(height: isTablet ? 24 : 18),
|
|
Divider(color: Colors.grey.shade300, thickness: 1),
|
|
SizedBox(height: isTablet ? 10 : 6),
|
|
|
|
// Align(
|
|
// alignment: Alignment.centerRight,
|
|
// child: Text(
|
|
// "Installment #${inst['id'] ?? ''}",
|
|
// style: TextStyle(
|
|
// fontSize: isTablet ? 15 : 13,
|
|
// color: Colors.grey.shade600,
|
|
// fontWeight: FontWeight.w500,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Responsive info row builder
|
|
Widget buildInfoRow(IconData icon, String label, String value, bool isTablet) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: isTablet ? 24 : 20, color: Colors.grey.shade600),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
"$label:",
|
|
style: TextStyle(
|
|
fontSize: isTablet ? 17 : 15,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: isTablet ? 16 : 15,
|
|
color: Colors.grey.shade800,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|