175 lines
6.1 KiB
Dart
175 lines
6.1 KiB
Dart
// fuel_drawdowns_page.dart — حركة القسائم وملخّص الائتمان
|
|
//
|
|
// الشاشة التي تُجيب: كم أقرضنا؟ كم استُرد؟ وكم بقي في الشارع؟
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import '../../../controller/fuel/fuel_admin_controller.dart';
|
|
import '../../../controller/fuel/fuel_admin_models.dart';
|
|
|
|
class FuelDrawdownsPageView extends StatefulWidget {
|
|
const FuelDrawdownsPageView({super.key});
|
|
|
|
@override
|
|
State<FuelDrawdownsPageView> createState() => _FuelDrawdownsPageViewState();
|
|
}
|
|
|
|
class _FuelDrawdownsPageViewState extends State<FuelDrawdownsPageView> {
|
|
String? _status;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
Get.find<FuelAdminController>().fetchDrawdowns();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
|
|
return GetBuilder<FuelAdminController>(
|
|
builder: (c) => Scaffold(
|
|
backgroundColor: cs.surface,
|
|
appBar: AppBar(
|
|
backgroundColor: cs.surface,
|
|
foregroundColor: cs.onSurface,
|
|
title: const Text('حركة القسائم'),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
if (c.drawdowns != null) _summary(c.drawdowns!, cs),
|
|
_filters(c, cs),
|
|
Expanded(
|
|
child: c.isLoadingDrawdowns
|
|
? Center(child: CircularProgressIndicator(color: cs.primary))
|
|
: (c.drawdowns?.vouchers.isEmpty ?? true)
|
|
? Center(
|
|
child: Text('لا توجد قسائم',
|
|
style: TextStyle(color: cs.onSurfaceVariant)))
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
itemCount: c.drawdowns!.vouchers.length,
|
|
itemBuilder: (_, i) =>
|
|
_voucherCard(c.drawdowns!.vouchers[i], cs),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// الملخّص على كامل المنتج لا على القائمة المعروضة: رقم يتغيّر بتغيّر
|
|
/// الفلتر يُقرأ كأنه حقيقة عن المنصة وهو ليس كذلك.
|
|
Widget _summary(FuelDrawdownsPage d, ColorScheme cs) => Container(
|
|
margin: const EdgeInsets.fromLTRB(12, 12, 12, 4),
|
|
padding: const EdgeInsets.all(14),
|
|
decoration: BoxDecoration(
|
|
color: cs.surfaceContainerHighest,
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
_stat('أُقرض', d.lentTotal.toString(), cs.onSurface, cs),
|
|
_stat('استُرد', d.collectedTotal.toString(), cs.primary, cs),
|
|
_stat('قائم', d.outstandingTotal.toString(), cs.tertiary, cs),
|
|
_stat('سائق', d.driversCount.toString(), cs.onSurfaceVariant, cs),
|
|
],
|
|
),
|
|
);
|
|
|
|
Widget _stat(String label, String value, Color color, ColorScheme cs) =>
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(value,
|
|
style: TextStyle(
|
|
color: color, fontSize: 16, fontWeight: FontWeight.w700)),
|
|
const SizedBox(height: 2),
|
|
Text(label,
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11)),
|
|
],
|
|
),
|
|
);
|
|
|
|
Widget _filters(FuelAdminController c, ColorScheme cs) {
|
|
const options = {
|
|
null: 'الكل',
|
|
'issued': 'سارية',
|
|
'redeemed': 'مصروفة',
|
|
'expired': 'منتهية',
|
|
'cancelled': 'ملغاة',
|
|
};
|
|
|
|
return SizedBox(
|
|
height: 46,
|
|
child: ListView(
|
|
scrollDirection: Axis.horizontal,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
children: options.entries.map((e) {
|
|
final selected = _status == e.key;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 8),
|
|
child: ChoiceChip(
|
|
label: Text(e.value),
|
|
selected: selected,
|
|
onSelected: (_) {
|
|
setState(() => _status = e.key);
|
|
c.fetchDrawdowns(status: e.key);
|
|
},
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _voucherCard(FuelVoucher v, ColorScheme cs) {
|
|
final (color, label) = switch (v.status) {
|
|
'redeemed' => (cs.primary, 'مصروفة'),
|
|
'issued' => (cs.tertiary, 'سارية'),
|
|
'cancelled' => (cs.error, 'ملغاة'),
|
|
_ => (cs.onSurfaceVariant, 'منتهية'),
|
|
};
|
|
|
|
// المصروف لا المأذون: السائق قد يطلب عشرين ويتزوّد بخمسة عشر،
|
|
// والدَّين على ما صُرف. عرض المأذون هنا يضخّم الأرقام في عين القارئ.
|
|
final amount = v.status == 'redeemed' ? v.amountRedeemed : v.amountAuthorized;
|
|
|
|
return Card(
|
|
color: cs.surfaceContainerHighest,
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
child: ListTile(
|
|
title: Row(
|
|
children: [
|
|
Text('$amount ${v.currency}',
|
|
style: TextStyle(
|
|
color: cs.onSurface, fontWeight: FontWeight.bold)),
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.15),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text(label, style: TextStyle(color: color, fontSize: 11)),
|
|
),
|
|
],
|
|
),
|
|
subtitle: Padding(
|
|
padding: const EdgeInsets.only(top: 4),
|
|
child: Text(
|
|
[
|
|
'سائق ${v.driverId}',
|
|
if (v.station != null && v.station!.isNotEmpty) v.station!,
|
|
v.redeemedAt ?? v.createdAt ?? '',
|
|
].where((s) => s.isNotEmpty).join(' · '),
|
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|