cdb_ui/lib/pages/transaction.dart
2024-10-08 10:55:30 +02:00

378 lines
12 KiB
Dart

import 'package:cdb_ui/api.dart';
import 'package:cdb_ui/pages/consume.dart';
import 'package:cdb_ui/pages/supply.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:qr_bar_code/qr/qr.dart';
class TransactionPage extends StatefulWidget {
final Transaction transaction;
final Function? refresh;
const TransactionPage(this.transaction, {this.refresh, super.key});
@override
State<TransactionPage> createState() => _TransactionPageState();
}
class _TransactionPageState extends State<TransactionPage> {
late Transaction transaction;
@override
void initState() {
super.initState();
transaction = widget.transaction;
}
Future<void> reload() async {
if (widget.refresh != null) {
widget.refresh!();
}
var updateTransaction = await API().getTransaction(transaction.uuid);
setState(() {
transaction = updateTransaction;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(API().getItem(transaction.item).name),
actions: [
IconButton(
onPressed: () {
var locations = API().getLocations();
List<String> locationList = locations.keys.toList();
String? selectedLocationID;
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Move Transaction'),
content: Row(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton<String>(
value: selectedLocationID,
onChanged: (value) {
selectedLocationID = value!;
API()
.moveTransaction(widget.transaction.uuid,
selectedLocationID!)
.then((x) {
Navigator.of(context).pop();
});
setState(() {});
},
items: locationList
.map<DropdownMenuItem<String>>((locationID) {
return DropdownMenuItem<String>(
value: locationID,
child: Text(locations[locationID]!.name),
);
}).toList(),
),
IconButton(
onPressed: () async {
var locations = API().getLocations();
var code = await scanQRCode(context,
title: "Scan Location Code");
if (!locations.containsKey(code)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'The location $code does not exist.')),
);
return;
}
API()
.moveTransaction(widget.transaction.uuid,
selectedLocationID!)
.then(
(x) {
Navigator.of(context).pop();
},
);
setState(() {});
},
icon: const Icon(Icons.qr_code))
],
),
);
},
);
},
icon: const Icon(Icons.move_up))
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
children: [
Row(
children: [
QRCode(
data: transaction.uuid,
size: 128,
eyeStyle: const QREyeStyle(color: Colors.white),
dataModuleStyle: const QRDataModuleStyle(color: Colors.white),
semanticsLabel: "Transaction UUID",
),
const SizedBox(
width: 16.0,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// todo : human names
Text(
API().getItem(transaction.item).name,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 28),
),
Text(
API()
.getItem(transaction.item)
.variants[transaction.variant]!
.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.grey),
),
const SizedBox(
height: 8.0,
),
Text("Added: ${tsFormat(transaction.timestamp)}"),
],
)
],
),
const Divider(),
const SizedBox(
height: 12.0,
),
if (transaction.expired) const Text("Transaction is Expired!"),
IconText(Icons.money, transaction.price.toStringAsFixed(2),
color: Colors.green),
if (transaction.origin != null)
IconText(Icons.store, transaction.origin!, color: Colors.blue),
if (transaction.location != null)
IconText(Icons.location_city, transaction.location!.name),
if (transaction.note != null) Text(transaction.note!),
if (transaction.consumed != null) ...[
const Divider(),
Text("Consumed on: ${tsFormat(transaction.consumed!.timestamp)}"),
IconText(Icons.store, transaction.consumed!.destination,
color: Colors.blue),
IconText(
Icons.money, transaction.consumed!.price.toStringAsFixed(2),
color: Colors.green),
]
// todo : chart with price history
],
),
),
floatingActionButton: transaction.consumed == null
? FloatingActionButton(
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return ConsumePage(transaction, reload);
},
));
},
child: const Icon(Icons.receipt_long))
: null,
);
}
}
class TransactionCard extends StatelessWidget {
final Transaction t;
final Function refresh;
final Function(Transaction)? onTap;
final Function(Transaction)? onLongPress;
const TransactionCard(this.t, this.refresh,
{this.onTap, this.onLongPress, super.key});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
if (onTap != null) {
onTap!(t);
return;
}
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return ConsumePage(t, refresh);
},
));
},
onLongPress: () {
if (onLongPress != null) {
onLongPress!(t);
return;
}
Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return TransactionPage(t);
},
));
},
child: Card(
color: t.expired ? Colors.red[100] : Colors.black,
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
API().getItem(t.item).name,
style: const TextStyle(fontSize: 16),
),
const SizedBox(
width: 8,
),
Text(
API().getItem(t.item).variants[t.variant]!.name,
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
),
],
),
if (t.timestamp != 0)
Text(
tsFormat(t.timestamp),
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
),
],
),
if ((t.note ?? "").isNotEmpty) ...[
const SizedBox(
height: 4,
),
Text(t.note!)
],
const SizedBox(
height: 10,
),
IconText(Icons.money, "${t.price.toStringAsFixed(2)}",
color: Colors.green),
if (t.origin != null) ...[
const SizedBox(height: 8),
IconText(Icons.store, t.origin!, color: Colors.blue)
],
if (t.location != null) ...[
const SizedBox(
height: 8,
),
IconText(Icons.location_city, t.location!.name)
]
],
),
),
),
);
}
}
String tsFormat(int ts) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(ts * 1000);
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dateTime);
}
class IconText extends StatelessWidget {
final IconData icon;
final String text;
final Color? color;
const IconText(this.icon, this.text, {super.key, this.color});
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(icon, size: 18, color: color),
const SizedBox(width: 6),
Text(
text,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
overflow: TextOverflow.fade,
),
],
);
}
}
class TransactionSelectPage extends StatelessWidget {
final Function(Transaction) onSelect;
final List<Transaction> selections;
final List<Transaction>? exclude;
const TransactionSelectPage(this.selections,
{super.key, required this.onSelect, this.exclude});
@override
Widget build(BuildContext context) {
var selectionList = [];
for (var s in selections) {
if (exclude?.any((x) => x.uuid == s.uuid) ?? false) {
continue;
}
selectionList.add(s);
}
return Scaffold(
appBar: AppBar(
title: const Text("Select a Transaction"),
),
body: ListView(
children: selectionList.isEmpty
? [
const ListTile(
title: Center(child: Text("No Transactions available")),
)
]
: selectionList
.map((x) => TransactionCard(
x,
() {},
onLongPress: (x) {},
onTap: (t) {
onSelect(t);
Navigator.of(context).pop();
},
))
.toList()),
);
}
}