Compare commits
10 commits
4b2c8711df
...
e366960ce7
Author | SHA1 | Date | |
---|---|---|---|
e366960ce7 | |||
7ee1cda550 | |||
61134f7b63 | |||
0e2ccb9e54 | |||
fcecb5aad5 | |||
b13b385cb7 | |||
d21554cddc | |||
167a4620fe | |||
2f24e02696 | |||
a608c50447 |
21 changed files with 601 additions and 429 deletions
|
@ -41,8 +41,7 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.cdb_ui"
|
||||
applicationId "de.hydrar.red.cdb"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="cdb_ui"
|
||||
android:label="CDB"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
|
201
lib/api.dart
201
lib/api.dart
|
@ -6,7 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
// todo : api errors
|
||||
|
||||
class API {
|
||||
late SharedPreferences pref;
|
||||
SharedPreferences? pref;
|
||||
static final API _instance = API._internal();
|
||||
|
||||
// cache
|
||||
|
@ -20,18 +20,46 @@ class API {
|
|||
|
||||
API._internal();
|
||||
|
||||
Future<void> init() async {
|
||||
Future<void> prefetch() async {
|
||||
// todo : prefetch
|
||||
// fetch items
|
||||
var resp = jsonDecode(await getRequest("$instance/items"));
|
||||
var lst = resp["items"] as List<dynamic>;
|
||||
items = lst.map((x) => Item(x)).toList();
|
||||
|
||||
// fetch locations
|
||||
var locResp = jsonDecode(await getRequest("$instance/locations"))
|
||||
as Map<String, dynamic>;
|
||||
locations = locResp.map((key, value) => MapEntry(key, Location(value)));
|
||||
|
||||
// fetch flowInfos
|
||||
var flowResp =
|
||||
jsonDecode(await getRequest("$instance/flows")) as Map<String, dynamic>;
|
||||
|
||||
flowInfos = flowResp.map((key, value) => MapEntry(key, FlowInfo(value)));
|
||||
}
|
||||
|
||||
Future<void> init(Function refresh) async {
|
||||
pref = await SharedPreferences.getInstance();
|
||||
instance = pref.getString("instance") ?? "";
|
||||
instance = pref!.getString("instance") ?? "";
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool isInit() {
|
||||
return pref.containsKey("token") && pref.containsKey("instance");
|
||||
if (pref == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pref!.containsKey("token") && pref!.containsKey("instance");
|
||||
}
|
||||
|
||||
bool isPrefetched() {
|
||||
return items != null && locations != null && flowInfos != null;
|
||||
}
|
||||
|
||||
void save(String instance, String token) {
|
||||
pref.setString("instance", instance);
|
||||
pref.setString("token", token);
|
||||
pref!.setString("instance", instance);
|
||||
pref!.setString("token", token);
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
|
@ -41,7 +69,7 @@ class API {
|
|||
var resp = await http.get(Uri.parse(url), headers: <String, String>{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Token': pref.getString("token")!
|
||||
'Token': pref!.getString("token")!
|
||||
});
|
||||
|
||||
return utf8.decode(resp.bodyBytes);
|
||||
|
@ -52,7 +80,7 @@ class API {
|
|||
headers: <String, String>{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Token': pref.getString("token")!
|
||||
'Token': pref!.getString("token")!
|
||||
},
|
||||
body: jsonEncode(data));
|
||||
|
||||
|
@ -60,14 +88,7 @@ class API {
|
|||
}
|
||||
|
||||
// /items
|
||||
Future<List<Item>> getItems() async {
|
||||
if (items != null) {
|
||||
return items!;
|
||||
}
|
||||
|
||||
var resp = jsonDecode(await getRequest("$instance/items"));
|
||||
var lst = resp["items"] as List<dynamic>;
|
||||
items = lst.map((x) => Item(x)).toList();
|
||||
List<Item> getItems() {
|
||||
return items!;
|
||||
}
|
||||
|
||||
|
@ -90,23 +111,12 @@ class API {
|
|||
return ret;
|
||||
}
|
||||
|
||||
Future<Map<String, Location>> getLocations() async {
|
||||
if (locations != null) {
|
||||
return locations!;
|
||||
}
|
||||
|
||||
var resp = jsonDecode(await getRequest("$instance/locations"))
|
||||
as Map<String, dynamic>;
|
||||
locations = resp.map((key, value) => MapEntry(key, Location(value)));
|
||||
Map<String, Location> getLocations() {
|
||||
return locations!;
|
||||
}
|
||||
|
||||
Future<Item> getItem(String item) async {
|
||||
if (items != null) {
|
||||
return items!.firstWhere((x) => x.id == item);
|
||||
}
|
||||
|
||||
return Item(jsonDecode(await getRequest("$instance/item/$item")));
|
||||
Item getItem(String item) {
|
||||
return items!.firstWhere((x) => x.id == item);
|
||||
}
|
||||
|
||||
Future<Transaction> getTransaction(String id) async {
|
||||
|
@ -162,7 +172,7 @@ class API {
|
|||
}
|
||||
|
||||
// /supply
|
||||
Future<String> supplyItem(String item, String variant, String price,
|
||||
Future<String> supplyItem(String item, String variant, double price,
|
||||
String? origin, String? location, String? note) async {
|
||||
if (origin!.isEmpty) {
|
||||
origin = null;
|
||||
|
@ -191,13 +201,19 @@ class API {
|
|||
|
||||
// /demand
|
||||
Future<void> consumeItem(
|
||||
String transaction, String destination, String price) async {
|
||||
String transaction, String destination, double price) async {
|
||||
await postRequest("$instance/demand",
|
||||
{"uuid": transaction, "destination": destination, "price": price});
|
||||
}
|
||||
|
||||
// /item/<item_id>/<variant_id>/stat
|
||||
Future<ItemVariantStat> getStat(String item, String variant) async {
|
||||
Future<dynamic> getStat(String item, String variant,
|
||||
{bool full = false}) async {
|
||||
if (full) {
|
||||
return FullItemVariantStat(jsonDecode(
|
||||
await getRequest("$instance/item/$item/$variant/stat?full=true")));
|
||||
}
|
||||
|
||||
return ItemVariantStat(
|
||||
jsonDecode(await getRequest("$instance/item/$item/$variant/stat")));
|
||||
}
|
||||
|
@ -207,7 +223,7 @@ class API {
|
|||
}
|
||||
|
||||
// /item/<item_id>/<variant_id>/price_history?<origin>
|
||||
Future<List<Price>> getPriceHistory(String item, String variant,
|
||||
Future<List<double>> getPriceHistory(String item, String variant,
|
||||
{String? origin}) async {
|
||||
var url = "$instance/item/$item/$variant/price_history";
|
||||
|
||||
|
@ -217,10 +233,10 @@ class API {
|
|||
|
||||
var resp = jsonDecode(await getRequest(url)) as List<dynamic>;
|
||||
|
||||
return resp.map((x) => Price(x)).toList();
|
||||
return resp.map((x) => x as double).toList();
|
||||
}
|
||||
|
||||
Future<Price> getLatestPrice(String item, String variant,
|
||||
Future<double> getLatestPrice(String item, String variant,
|
||||
{String? origin}) async {
|
||||
var url = "$instance/item/$item/$variant/price_latest";
|
||||
|
||||
|
@ -230,31 +246,19 @@ class API {
|
|||
|
||||
var resp = jsonDecode(await getRequest(url)) as Map<String, dynamic>;
|
||||
|
||||
return Price(resp);
|
||||
return resp as double;
|
||||
}
|
||||
|
||||
// Flows
|
||||
|
||||
// /flows
|
||||
Future<Map<String, FlowInfo>> getFlows() async {
|
||||
if (flowInfos != null) {
|
||||
return flowInfos!;
|
||||
}
|
||||
|
||||
var resp =
|
||||
jsonDecode(await getRequest("$instance/flows")) as Map<String, dynamic>;
|
||||
|
||||
flowInfos = resp.map((key, value) => MapEntry(key, FlowInfo(value)));
|
||||
Map<String, FlowInfo> getFlows() {
|
||||
return flowInfos!;
|
||||
}
|
||||
|
||||
// /flow/<id>/info
|
||||
Future<FlowInfo> getFlowInfo(String id) async {
|
||||
if (flowInfos != null) {
|
||||
return flowInfos![id]!;
|
||||
}
|
||||
|
||||
return FlowInfo(jsonDecode(await getRequest("$instance/flow/$id/info")));
|
||||
FlowInfo getFlowInfo(String id) {
|
||||
return flowInfos![id]!;
|
||||
}
|
||||
|
||||
Future<Flow> getFlow(String id) async {
|
||||
|
@ -277,12 +281,18 @@ class API {
|
|||
}
|
||||
|
||||
// /flow/<id>/end
|
||||
Future<List<String>?> endFlow(String id, {List<SupplyForm>? produced}) async {
|
||||
Future<Map<String, List<String>>?> endFlow(String id,
|
||||
{List<SupplyForm>? produced}) async {
|
||||
var resp = jsonDecode(await postRequest("$instance/flow/$id/end",
|
||||
{"produced": produced?.map((x) => x.json()).toList()}));
|
||||
|
||||
if (produced != null) {
|
||||
return (resp["produced"] as List<dynamic>).cast<String>();
|
||||
var produced = resp["produced"] as Map<String, dynamic>;
|
||||
return produced.map(
|
||||
(key, value) {
|
||||
return MapEntry(key, (value as List<dynamic>).cast<String>());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -314,13 +324,8 @@ class API {
|
|||
"$instance/transaction/$id/move", {"to": newLocation}));
|
||||
}
|
||||
|
||||
Future<Location> getLocation(String id) async {
|
||||
if (locations != null) {
|
||||
return locations![id]!;
|
||||
}
|
||||
|
||||
var resp = jsonDecode(await getRequest("$instance/location/$id"));
|
||||
return Location(resp);
|
||||
Location getLocation(String id) {
|
||||
return locations![id]!;
|
||||
}
|
||||
|
||||
Future<String> addNoteToFlow(String flowID, String content) async {
|
||||
|
@ -356,12 +361,14 @@ class FlowInfo {
|
|||
|
||||
class Item {
|
||||
late String id;
|
||||
late String? image;
|
||||
late String name;
|
||||
late String? category;
|
||||
late Map<String, ItemVariant> variants;
|
||||
|
||||
Item(Map<String, dynamic> json) {
|
||||
id = json["uuid"];
|
||||
image = json["image"];
|
||||
name = json["name"];
|
||||
category = json["category"];
|
||||
variants = <String, ItemVariant>{};
|
||||
|
@ -378,6 +385,7 @@ class ItemVariant {
|
|||
late String name;
|
||||
int? min;
|
||||
int? expiry;
|
||||
List<int>? barcodes;
|
||||
|
||||
ItemVariant(Map<String, dynamic> json) {
|
||||
item = json["item"];
|
||||
|
@ -385,26 +393,9 @@ class ItemVariant {
|
|||
name = json["name"];
|
||||
min = json["min"];
|
||||
expiry = json["expiry"];
|
||||
}
|
||||
}
|
||||
|
||||
class Price {
|
||||
late double value;
|
||||
late String currency;
|
||||
|
||||
Price(Map<String, dynamic> json) {
|
||||
value = json["value"];
|
||||
currency = json["currency"];
|
||||
}
|
||||
|
||||
Price.fromString(String value) {
|
||||
var priceSplit = value.split(" ");
|
||||
this.value = double.parse(priceSplit[0]);
|
||||
currency = priceSplit[1];
|
||||
}
|
||||
|
||||
String format() {
|
||||
return "${value.toStringAsFixed(2)} $currency";
|
||||
barcodes = json["barcodes"] != null
|
||||
? (json["barcodes"] as List<dynamic>).cast<int>()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,7 +403,7 @@ class Transaction {
|
|||
late String uuid;
|
||||
late String item;
|
||||
late String variant;
|
||||
late Price price;
|
||||
late double price;
|
||||
String? origin;
|
||||
late int timestamp;
|
||||
ConsumeInfo? consumed;
|
||||
|
@ -424,7 +415,7 @@ class Transaction {
|
|||
uuid = json["uuid"];
|
||||
item = json["item"];
|
||||
variant = json["variant"];
|
||||
price = Price(json["price"]);
|
||||
price = json["price"];
|
||||
origin = json["origin"];
|
||||
timestamp = json["timestamp"];
|
||||
expired = json["expired"];
|
||||
|
@ -433,12 +424,11 @@ class Transaction {
|
|||
location = json["location"] != null ? Location(json["location"]) : null;
|
||||
}
|
||||
|
||||
Transaction.inMemory(String itemID, String variantID, String price,
|
||||
Transaction.inMemory(String itemID, String variantID, this.price,
|
||||
String? origin, Location? location, String? note) {
|
||||
uuid = "";
|
||||
item = itemID;
|
||||
variant = variantID;
|
||||
this.price = Price.fromString(price);
|
||||
origin = origin;
|
||||
timestamp = 0;
|
||||
consumed = null;
|
||||
|
@ -450,12 +440,12 @@ class Transaction {
|
|||
|
||||
class ConsumeInfo {
|
||||
late String destination;
|
||||
late Price price;
|
||||
late double price;
|
||||
late int timestamp;
|
||||
|
||||
ConsumeInfo(Map<String, dynamic> json) {
|
||||
destination = json["destination"];
|
||||
price = Price(json["price"]);
|
||||
price = json["price"];
|
||||
timestamp = json["timestamp"];
|
||||
}
|
||||
}
|
||||
|
@ -563,14 +553,14 @@ class FlowNote {
|
|||
}
|
||||
|
||||
class GlobalItemStat {
|
||||
late int item_count;
|
||||
late int total_transactions;
|
||||
late double total_price;
|
||||
late int itemCount;
|
||||
late int totalTransactions;
|
||||
late double totalPrice;
|
||||
|
||||
GlobalItemStat(Map<String, dynamic> json) {
|
||||
item_count = json["item_count"];
|
||||
total_transactions = json["total_transactions"];
|
||||
total_price = json["total_price"];
|
||||
itemCount = json["item_count"];
|
||||
totalTransactions = json["total_transactions"];
|
||||
totalPrice = json["total_price"];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,3 +568,28 @@ class GlobalItemStat {
|
|||
var split = iv.split("::");
|
||||
return (split[0], split[1]);
|
||||
}
|
||||
|
||||
class FullItemVariantStat {
|
||||
late int amount;
|
||||
late double totalPrice;
|
||||
late double expiryRate;
|
||||
late Map<String, OriginStat> origins;
|
||||
|
||||
FullItemVariantStat(Map<String, dynamic> json) {
|
||||
amount = json["amount"];
|
||||
totalPrice = json["total_price"];
|
||||
expiryRate = json["expiry_rate"];
|
||||
origins = (json["origins"] as Map<String, dynamic>)
|
||||
.map((key, value) => MapEntry(key, OriginStat(value)));
|
||||
}
|
||||
}
|
||||
|
||||
class OriginStat {
|
||||
late double averagePrice;
|
||||
late int inventory;
|
||||
|
||||
OriginStat(Map<String, dynamic> json) {
|
||||
averagePrice = json["average_price"];
|
||||
inventory = json["inventory"];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,24 +7,53 @@ import 'package:cdb_ui/pages/stats.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await API().init();
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
bool init = false;
|
||||
|
||||
refresh() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
() async {
|
||||
await API().init(refresh);
|
||||
if (API().isInit()) {
|
||||
await API().prefetch();
|
||||
setState(() {
|
||||
init = true;
|
||||
});
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'CDB',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: API().isInit() ? const MyHomePage() : const SetupPage(),
|
||||
);
|
||||
title: 'CDB',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.deepPurple, brightness: Brightness.dark),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: API().isInit()
|
||||
? (API().isPrefetched()
|
||||
? const MyHomePage()
|
||||
: const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
))
|
||||
: const SetupPage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ class _ConsumePageState extends State<ConsumePage> {
|
|||
_formKey.currentState!.save();
|
||||
|
||||
API()
|
||||
.consumeItem(
|
||||
widget.transaction.uuid, _selectedDestination, "$_price €")
|
||||
.consumeItem(widget.transaction.uuid, _selectedDestination,
|
||||
double.parse(_price))
|
||||
.then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Item consumed successfully!')),
|
||||
|
|
|
@ -56,7 +56,7 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
|
|||
),
|
||||
);
|
||||
|
||||
if (confirm) {
|
||||
if (confirm ?? false) {
|
||||
await API
|
||||
.API()
|
||||
.endFlow(widget.flow.id)
|
||||
|
@ -67,23 +67,22 @@ class _ActiveFlowPageState extends State<ActiveFlowPage> {
|
|||
if (widget.info.next != null)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
API.API().getFlowInfo(widget.info.next!).then((newInfo) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return CreateFlowPage(
|
||||
newInfo,
|
||||
() {},
|
||||
previousFlow: widget.flow,
|
||||
);
|
||||
},
|
||||
));
|
||||
});
|
||||
var newInfo = API.API().getFlowInfo(widget.info.next!);
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return CreateFlowPage(
|
||||
newInfo,
|
||||
() {},
|
||||
previousFlow: widget.flow,
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
icon: const Icon(Icons.arrow_forward)),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 18.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
|
|
|
@ -25,11 +25,10 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
|
|||
input: depends.map((x) => x.uuid).toList())
|
||||
.then((x) {
|
||||
API.API().getFlow(x).then((flow) {
|
||||
API.API().getFlowInfo(flow.kind).then((info) {
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) => ActiveFlowPage(flow, info),
|
||||
));
|
||||
});
|
||||
var info = API.API().getFlowInfo(flow.kind);
|
||||
Navigator.of(context).pushReplacement(MaterialPageRoute(
|
||||
builder: (context) => ActiveFlowPage(flow, info),
|
||||
));
|
||||
});
|
||||
});
|
||||
return;
|
||||
|
@ -65,26 +64,42 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
|
|||
});
|
||||
}
|
||||
|
||||
Widget buildInputSelection(BuildContext context) {
|
||||
return Column(
|
||||
children: widget.info.depends.map((x) {
|
||||
return ElevatedButton(
|
||||
onPressed: () {
|
||||
selectDependItems(context, x);
|
||||
},
|
||||
child: Text("Add $x"));
|
||||
}).toList());
|
||||
List<Widget> buildInputSelection(BuildContext context) {
|
||||
if (widget.info.depends.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
Column(
|
||||
children: widget.info.depends.map((x) {
|
||||
var (item, variant) = API.itemVariant(x);
|
||||
return ElevatedButton(
|
||||
onPressed: () {
|
||||
selectDependItems(context, x);
|
||||
},
|
||||
child:
|
||||
Text("Add ${API.API().getItem(item).variants[variant]!.name}"));
|
||||
}).toList()),
|
||||
const Divider(),
|
||||
];
|
||||
}
|
||||
|
||||
Widget flowContinuation() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Card(
|
||||
child: Text(widget.previousFlow!.kind),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(API.API().getFlowInfo(widget.previousFlow!.kind).name),
|
||||
),
|
||||
),
|
||||
const Text(" -> "),
|
||||
const Card(child: Icon(Icons.arrow_right)),
|
||||
Card(
|
||||
child: Text(widget.info.name),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(widget.info.name),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
@ -93,15 +108,17 @@ class _CreateFlowPageState extends State<CreateFlowPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text("Create new ${widget.info.name} Flow")),
|
||||
appBar: AppBar(
|
||||
title: Text(widget.previousFlow != null
|
||||
? "Continue to ${widget.previousFlow!.kind}"
|
||||
: "Create new ${widget.info.name} Flow")),
|
||||
body: Column(
|
||||
children: [
|
||||
if (widget.previousFlow != null) ...[
|
||||
flowContinuation(),
|
||||
const Divider()
|
||||
],
|
||||
buildInputSelection(context),
|
||||
const Divider(),
|
||||
...buildInputSelection(context),
|
||||
Card(
|
||||
child: Column(
|
||||
children: depends
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:js_interop_unsafe';
|
||||
|
||||
import 'package:cdb_ui/api.dart' as API;
|
||||
import 'package:cdb_ui/api.dart';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
|
@ -23,13 +21,7 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
API.API().getLocations().then(
|
||||
(value) {
|
||||
setState(() {
|
||||
locations = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
locations = API.API().getLocations();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
@ -46,25 +38,24 @@ class _EndFlowWithProduceState extends State<EndFlowWithProduce> {
|
|||
List<Widget> ret = [];
|
||||
|
||||
for (var i in widget.info.produces!) {
|
||||
var (itemID, variant) = API.itemVariant(i);
|
||||
var item = API.API().getItem(itemID);
|
||||
ret.add(ElevatedButton(
|
||||
onPressed: () {
|
||||
var (itemID, variant) = API.itemVariant(i);
|
||||
API.API().getItem(itemID).then((item) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return SupplyPage(
|
||||
item,
|
||||
refresh,
|
||||
onlyVariants: [variant],
|
||||
forcePrice: "0.00",
|
||||
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
|
||||
onCreate: addProduced,
|
||||
);
|
||||
},
|
||||
));
|
||||
});
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return SupplyPage(
|
||||
item,
|
||||
refresh,
|
||||
onlyVariants: [variant],
|
||||
forcePrice: "0.00",
|
||||
forceOrigin: "flow::${widget.flow.kind}::${widget.flow.id}",
|
||||
onCreate: addProduced,
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
child: Text("Produced $i")));
|
||||
child: Text("Produced ${item.variants[variant]!.name}")));
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -24,13 +24,17 @@ class _FlowInfoPageState extends State<FlowInfoPage> {
|
|||
body: Column(
|
||||
children: [
|
||||
// todo : ui improve
|
||||
if (widget.info.next != null) Text("Next: ${widget.info.next}"),
|
||||
if (widget.info.next != null)
|
||||
Text("Next: ${API.API().getFlowInfo(widget.info.next!).name}"),
|
||||
if (widget.info.depends.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const Text("Flow can use: "),
|
||||
...widget.info.depends.map((x) => Text(x)).toList(),
|
||||
...widget.info.depends.map((x) {
|
||||
var (item, variant) = API.itemVariant(x);
|
||||
return Text(API.API().getItem(item).variants[variant]!.name);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
if (widget.info.produces?.isNotEmpty ?? false)
|
||||
|
@ -38,7 +42,10 @@ class _FlowInfoPageState extends State<FlowInfoPage> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const Text("Flow can produce: "),
|
||||
...widget.info.produces!.map((x) => Text(x)).toList(),
|
||||
...widget.info.produces!.map((x) {
|
||||
var (item, variant) = API.itemVariant(x);
|
||||
return Text(API.API().getItem(item).variants[variant]!.name);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
|
|
|
@ -2,9 +2,8 @@ import 'package:cdb_ui/api.dart' as API;
|
|||
import 'package:cdb_ui/pages/expandable_list.dart';
|
||||
import 'package:cdb_ui/pages/flow/active_flow_page.dart';
|
||||
import 'package:cdb_ui/pages/flow/flow_info_page.dart';
|
||||
import 'package:cdb_ui/pages/transaction.dart';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||
|
||||
class FlowsPage extends StatefulWidget {
|
||||
const FlowsPage({super.key});
|
||||
|
@ -20,13 +19,7 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
API.API().getFlows().then(
|
||||
(value) {
|
||||
setState(() {
|
||||
flowInfos = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
flowInfos = API.API().getFlows();
|
||||
}
|
||||
|
||||
Widget flowTile(BuildContext context, API.FlowInfo x) {
|
||||
|
@ -94,7 +87,8 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
children: producedMapping[key]!.map((x) {
|
||||
return flowTile(context, x);
|
||||
}).toList());
|
||||
items.add(ExpandableListItem(body: flows, header: Text(key)));
|
||||
items.add(ExpandableListItem(
|
||||
body: flows, header: Text(API.API().getItem(key).name)));
|
||||
}
|
||||
|
||||
return ExpandableList(items);
|
||||
|
@ -122,7 +116,8 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
children: dependsMapping[key]!.map((x) {
|
||||
return flowTile(context, x);
|
||||
}).toList());
|
||||
items.add(ExpandableListItem(body: flows, header: Text(key)));
|
||||
items.add(ExpandableListItem(
|
||||
body: flows, header: Text(API.API().getItem(key).name)));
|
||||
}
|
||||
|
||||
return ExpandableList(items);
|
||||
|
@ -160,25 +155,18 @@ class _FlowsPageState extends State<FlowsPage> {
|
|||
_ => const Text("..."),
|
||||
},
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// scan flow code
|
||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
||||
context: context,
|
||||
onCode: (code) {
|
||||
// library is retarded
|
||||
code = code!.replaceFirst("Code scanned = ", "");
|
||||
var code = await scanQRCode(context, title: "Scan Flow Code");
|
||||
|
||||
API.API().getFlow(code).then((flow) {
|
||||
API.API().getFlowInfo(flow.kind).then((info) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ActiveFlowPage(flow, info);
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
API.API().getFlow(code!).then((flow) {
|
||||
var info = API.API().getFlowInfo(flow.kind);
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ActiveFlowPage(flow, info);
|
||||
},
|
||||
));
|
||||
});
|
||||
},
|
||||
child: const Icon(Icons.qr_code),
|
||||
),
|
||||
|
|
|
@ -1,48 +1,61 @@
|
|||
import 'package:cdb_ui/api.dart';
|
||||
import 'package:cdb_ui/pages/itemview.dart';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
import 'package:cdb_ui/pages/transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||
|
||||
class ItemsPage extends StatelessWidget {
|
||||
const ItemsPage({super.key});
|
||||
|
||||
_scanBarcodeSupply(BuildContext context) async {
|
||||
var code =
|
||||
int.parse(await scanQRCode(context, title: "Scan Barcode") ?? "0");
|
||||
|
||||
var items = API().getItems();
|
||||
for (var item in items) {
|
||||
for (var variant in item.variants.keys) {
|
||||
for (var barcode in item.variants[variant]!.barcodes ?? []) {
|
||||
if (code == barcode) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => SupplyPage(
|
||||
item,
|
||||
() {},
|
||||
onlyVariants: [variant],
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Items"),
|
||||
// todo : add barcode scan
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
_scanBarcodeSupply(context);
|
||||
},
|
||||
child: const Icon(Icons.add_box))
|
||||
],
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: API().getItems(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
var items = snapshot.data!;
|
||||
|
||||
return ListView(
|
||||
children: items.map((x) {
|
||||
return ItemTile(x);
|
||||
}).toList());
|
||||
}),
|
||||
body: ListView(
|
||||
children: API().getItems().map((x) {
|
||||
return ItemTile(x);
|
||||
}).toList()),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// scan transaction code
|
||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
||||
context: context,
|
||||
onCode: (code) {
|
||||
// library is retarded
|
||||
code = code!.replaceFirst("Code scanned = ", "");
|
||||
var code = await scanQRCode(context, title: "Scan Transaction Code");
|
||||
|
||||
API().getTransaction(code).then((t) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => TransactionPage(t),
|
||||
));
|
||||
});
|
||||
},
|
||||
);
|
||||
API().getTransaction(code!).then((t) {
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => TransactionPage(t),
|
||||
));
|
||||
});
|
||||
},
|
||||
child: const Icon(Icons.qr_code),
|
||||
),
|
||||
|
|
|
@ -2,7 +2,6 @@ import 'package:cdb_ui/api.dart';
|
|||
import 'package:cdb_ui/pages/stats.dart';
|
||||
import 'package:cdb_ui/pages/transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'supply.dart';
|
||||
|
||||
// todo : show est. time remaining until inventory gets empty (based on demand)
|
||||
|
@ -33,7 +32,7 @@ class _ItemViewState extends State<ItemView> {
|
|||
builder: (context) => ItemStatPage(widget.item),
|
||||
));
|
||||
},
|
||||
icon: Icon(Icons.bar_chart))
|
||||
icon: const Icon(Icons.bar_chart))
|
||||
],
|
||||
),
|
||||
body: Column(children: [
|
||||
|
@ -44,12 +43,15 @@ class _ItemViewState extends State<ItemView> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Align(
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Placeholder(
|
||||
fallbackWidth: 100,
|
||||
fallbackHeight: 100,
|
||||
),
|
||||
child: widget.item.image != null
|
||||
? Image.network(
|
||||
"${API().instance}/${widget.item.image}",
|
||||
height: 100,
|
||||
width: 100,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16.0,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:cdb_ui/api.dart';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
import 'package:cdb_ui/pages/transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_simple_treeview/flutter_simple_treeview.dart';
|
||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||
|
||||
class LocationsPage extends StatefulWidget {
|
||||
const LocationsPage({super.key});
|
||||
|
@ -17,13 +17,7 @@ class _LocationsPageState extends State<LocationsPage> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
API().getLocations().then(
|
||||
(value) {
|
||||
setState(() {
|
||||
locations = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
locations = API().getLocations();
|
||||
}
|
||||
|
||||
TreeNode buildTree(BuildContext context, String locID) {
|
||||
|
@ -69,25 +63,20 @@ class _LocationsPageState extends State<LocationsPage> {
|
|||
return buildTree(context, key);
|
||||
}).toList()),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
// scan location code
|
||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
||||
context: context,
|
||||
onCode: (code) {
|
||||
// library is retarded
|
||||
code = code!.replaceFirst("Code scanned = ", "");
|
||||
if (!locations!.containsKey(code)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('The location $code does not exist.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
var code = await scanQRCode(context, title: "Scan Location Code");
|
||||
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => LocationView(locations![code]!),
|
||||
));
|
||||
},
|
||||
);
|
||||
if (!locations!.containsKey(code)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('The location $code does not exist.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (context) => LocationView(locations![code]!),
|
||||
));
|
||||
},
|
||||
child: const Icon(Icons.qr_code),
|
||||
),
|
||||
|
@ -118,7 +107,7 @@ class _LocationViewState extends State<LocationView> {
|
|||
title: Text(widget.location.name),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Card(
|
||||
|
|
|
@ -31,6 +31,8 @@ class _SetupPageState extends State<SetupPage> {
|
|||
const SnackBar(content: Text('Setup Complete!')),
|
||||
);
|
||||
|
||||
await API().prefetch();
|
||||
|
||||
// Navigate or close the setup screen
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cdb_ui/api.dart';
|
||||
import 'package:cdb_ui/pages/expandable_list.dart';
|
||||
import 'package:cdb_ui/pages/transaction.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -23,6 +24,9 @@ class StatsPage extends StatelessWidget {
|
|||
// global origin / destinations
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Home"),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _fetchData(),
|
||||
builder: (context, snapshot) {
|
||||
|
@ -45,26 +49,36 @@ class StatsPage extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconText(Icons.data_object,
|
||||
"Items: ${globalStat.item_count}"),
|
||||
"Items: ${globalStat.itemCount}"),
|
||||
IconText(Icons.list_alt,
|
||||
"Inventory: ${globalStat.total_transactions}"),
|
||||
"Inventory: ${globalStat.totalTransactions}"),
|
||||
IconText(Icons.money,
|
||||
"Price: ${globalStat.total_price.toStringAsFixed(2)} €")
|
||||
"Price: ${globalStat.totalPrice.toStringAsFixed(2)} €")
|
||||
],
|
||||
),
|
||||
)),
|
||||
|
||||
if (min.isNotEmpty)
|
||||
const ListTile(title: Text("Items under Minimum")),
|
||||
const ListTile(
|
||||
title: Text(
|
||||
"Items under Minimum",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
)),
|
||||
...min.map((item) {
|
||||
var (itemID, variant) = itemVariant(item.itemVariant);
|
||||
var name = API().getItem(itemID).variants[variant]!.name;
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
"Item ${item.itemVariant} under minimum. Needs ${item.need} more."),
|
||||
title: Text("$name under minimum. Needs ${item.need} more."),
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
if (expired.isNotEmpty)
|
||||
const ListTile(title: Text("Expired Items")),
|
||||
const ListTile(
|
||||
title: Text(
|
||||
"Expired Items",
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
)),
|
||||
|
||||
// Mapping expired list to widgets
|
||||
...expired.map((item) {
|
||||
|
@ -85,16 +99,48 @@ class ItemStatPage extends StatelessWidget {
|
|||
|
||||
const ItemStatPage(this.item, {super.key});
|
||||
|
||||
// todo : expiry ratio
|
||||
// todo : avg time of transaction active
|
||||
|
||||
ExpandableListItem buildVariantStat(String variant) {
|
||||
return ExpandableListItem(
|
||||
body: FutureBuilder(
|
||||
future: API().getStat(item.id, variant, full: true),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
var data = snapshot.data! as FullItemVariantStat;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Text("Amount: ${data.amount}"),
|
||||
Text("Total Cost: ${data.totalPrice}"),
|
||||
Text("Expiry Rate: ${data.expiryRate}"),
|
||||
...data.origins.keys.map((key) {
|
||||
var originStat = data.origins[key]!;
|
||||
return Column(children: [
|
||||
Text("Inventory: ${originStat.inventory}"),
|
||||
Text(
|
||||
"Average Price: ${originStat.averagePrice.toStringAsFixed(2)} €"),
|
||||
]);
|
||||
}).toList()
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
header: Text(item.variants[variant]!.name));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Statistics of ${item.name}"),
|
||||
),
|
||||
body: null,
|
||||
body: Column(children: [
|
||||
ExpandableList(item.variants.keys.map(buildVariantStat).toList())
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
import 'package:cdb_ui/api.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||
import 'package:simple_barcode_scanner/enum.dart';
|
||||
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
|
||||
|
||||
Future<String?> scanQRCode(BuildContext context,
|
||||
{String title = "Scan QR Code"}) async {
|
||||
var res = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SimpleBarcodeScannerPage(
|
||||
scanType: ScanType.qr,
|
||||
appBarTitle: title,
|
||||
),
|
||||
));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
class SupplyPage extends StatefulWidget {
|
||||
final Item item;
|
||||
|
@ -53,7 +68,7 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
var t = SupplyForm(
|
||||
itemID: widget.item.id,
|
||||
variant: variant,
|
||||
price: "${_priceController.text} €",
|
||||
price: double.parse(_priceController.text),
|
||||
origin: _selectedOrigin,
|
||||
location: _selectedLocation,
|
||||
note: _noteController.text);
|
||||
|
@ -66,8 +81,13 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
}
|
||||
|
||||
API()
|
||||
.supplyItem(widget.item.id, variant, "${_priceController.text} €",
|
||||
_selectedOrigin, _selectedLocation, _noteController.text)
|
||||
.supplyItem(
|
||||
widget.item.id,
|
||||
variant,
|
||||
double.parse(_priceController.text),
|
||||
_selectedOrigin,
|
||||
_selectedLocation,
|
||||
_noteController.text)
|
||||
.then((_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Item added successfully!')),
|
||||
|
@ -126,7 +146,7 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
const SizedBox(height: 16),
|
||||
|
||||
// Origin Field with Dropdown and Text Input
|
||||
if (widget.forceOrigin == null)
|
||||
if (widget.forceOrigin == null) ...[
|
||||
AutocompletedTextField(
|
||||
options: origins,
|
||||
getValue: () => _selectedOrigin,
|
||||
|
@ -138,7 +158,7 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
? await API()
|
||||
.getLatestPrice(widget.item.id, variant,
|
||||
origin: selection)
|
||||
.then((x) => x.value.toStringAsFixed(2))
|
||||
.then((x) => x.toStringAsFixed(2))
|
||||
: _priceController.text;
|
||||
setState(() {
|
||||
_priceController.text = price;
|
||||
|
@ -146,11 +166,11 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
});
|
||||
},
|
||||
label: "Origin"),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Price Field
|
||||
if (widget.forcePrice == null)
|
||||
if (widget.forcePrice == null) ...[
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(labelText: 'Price'),
|
||||
keyboardType: TextInputType.number,
|
||||
|
@ -165,8 +185,8 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// Location Dropdown
|
||||
Row(
|
||||
|
@ -197,15 +217,13 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
width: 12,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
QrBarCodeScannerDialog().getScannedQrBarCode(
|
||||
context: context,
|
||||
onCode: (code) {
|
||||
setState(() {
|
||||
_selectedLocation = code!;
|
||||
});
|
||||
},
|
||||
);
|
||||
onPressed: () async {
|
||||
var code = await scanQRCode(context);
|
||||
setState(() {
|
||||
if (API().getLocations().keys.contains(code)) {
|
||||
_selectedLocation = code!;
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.qr_code),
|
||||
),
|
||||
|
@ -235,7 +253,7 @@ class _SupplyPageState extends State<SupplyPage> {
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> _fetchData() async {
|
||||
var locations = await API().getLocations();
|
||||
var locations = API().getLocations();
|
||||
var origins = await API().getUniqueField(widget.item.id, variant, "origin");
|
||||
origins.insert(0, "");
|
||||
locations[""] = Location.zero();
|
||||
|
@ -293,7 +311,7 @@ class AutocompletedTextField extends StatelessWidget {
|
|||
class SupplyForm {
|
||||
final String itemID;
|
||||
final String variant;
|
||||
final String price;
|
||||
final double price;
|
||||
final String? origin;
|
||||
final String? location;
|
||||
final String note;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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';
|
||||
import 'package:qr_bar_code_scanner_dialog/qr_bar_code_scanner_dialog.dart';
|
||||
|
||||
class TransactionPage extends StatefulWidget {
|
||||
final Transaction transaction;
|
||||
|
@ -40,84 +40,73 @@ class _TransactionPageState extends State<TransactionPage> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.transaction.item),
|
||||
title: Text(API().getItem(transaction.item).name),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
API().getLocations().then((locations) {
|
||||
List<String> locationList = locations.keys.toList();
|
||||
String? selectedLocationID;
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
.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: () {
|
||||
API().getLocations().then((locations) {
|
||||
QrBarCodeScannerDialog()
|
||||
.getScannedQrBarCode(
|
||||
context: context,
|
||||
onCode: (code) {
|
||||
// library is retarded
|
||||
code = code!.replaceFirst(
|
||||
"Code scanned = ", "");
|
||||
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.qr_code))
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.move_up))
|
||||
],
|
||||
|
@ -143,12 +132,15 @@ class _TransactionPageState extends State<TransactionPage> {
|
|||
children: [
|
||||
// todo : human names
|
||||
Text(
|
||||
"${transaction.item}",
|
||||
API().getItem(transaction.item).name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 28),
|
||||
),
|
||||
Text(
|
||||
"${transaction.variant}",
|
||||
API()
|
||||
.getItem(transaction.item)
|
||||
.variants[transaction.variant]!
|
||||
.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
|
@ -169,7 +161,7 @@ class _TransactionPageState extends State<TransactionPage> {
|
|||
|
||||
if (transaction.expired) const Text("Transaction is Expired!"),
|
||||
|
||||
IconText(Icons.money, transaction.price.format(),
|
||||
IconText(Icons.money, transaction.price.toStringAsFixed(2),
|
||||
color: Colors.green),
|
||||
|
||||
if (transaction.origin != null)
|
||||
|
@ -185,7 +177,8 @@ class _TransactionPageState extends State<TransactionPage> {
|
|||
Text("Consumed on: ${tsFormat(transaction.consumed!.timestamp)}"),
|
||||
IconText(Icons.store, transaction.consumed!.destination,
|
||||
color: Colors.blue),
|
||||
IconText(Icons.money, transaction.consumed!.price.format(),
|
||||
IconText(
|
||||
Icons.money, transaction.consumed!.price.toStringAsFixed(2),
|
||||
color: Colors.green),
|
||||
]
|
||||
|
||||
|
@ -262,14 +255,14 @@ class TransactionCard extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
Text(
|
||||
t.item,
|
||||
API().getItem(t.item).name,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
t.variant,
|
||||
API().getItem(t.item).variants[t.variant]!.name,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
|
||||
),
|
||||
],
|
||||
|
@ -290,8 +283,7 @@ class TransactionCard extends StatelessWidget {
|
|||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
IconText(Icons.money,
|
||||
"${t.price.value.toStringAsFixed(2)} ${t.price.currency}",
|
||||
IconText(Icons.money, "${t.price.toStringAsFixed(2)} €",
|
||||
color: Colors.green),
|
||||
if (t.origin != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
@ -332,6 +324,7 @@ class IconText extends StatelessWidget {
|
|||
Text(
|
||||
text,
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -358,29 +351,28 @@ class TransactionSelectPage extends StatelessWidget {
|
|||
selectionList.add(s);
|
||||
}
|
||||
|
||||
if (selectionList.isEmpty) {
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No Transactions to select')),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Select a Transaction"),
|
||||
),
|
||||
body: ListView(
|
||||
children: selectionList
|
||||
.map((x) => TransactionCard(
|
||||
x,
|
||||
() {},
|
||||
onLongPress: (x) {},
|
||||
onTap: (t) {
|
||||
onSelect(t);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
))
|
||||
.toList()),
|
||||
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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
138
pubspec.lock
138
pubspec.lock
|
@ -94,6 +94,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_barcode_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_barcode_scanner
|
||||
sha256: a4ba37daf9933f451a5e812c753ddd045d6354e4a3280342d895b07fecaab3fa
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -102,6 +110,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.22"
|
||||
flutter_simple_treeview:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -124,10 +140,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -144,30 +160,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.18.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.4"
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -196,18 +204,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
version: "1.15.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -240,6 +248,54 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
permission_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "12.0.12"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.4.5"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -264,22 +320,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
qr_bar_code_scanner_dialog:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_bar_code_scanner_dialog
|
||||
sha256: dcb937816d4e562141530265bd1ca39fe00f57000fd79e26c163c957d443e9e4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.5"
|
||||
qr_code_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr_code_scanner
|
||||
sha256: f23b68d893505a424f0bd2e324ebea71ed88465d572d26bb8d2e78a4749591fd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -336,6 +376,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
simple_barcode_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: simple_barcode_scanner
|
||||
sha256: "52b30082ebd6fab1e6314cb9bfc1aca5372890616dcb89d0e254edf7b7ef4951"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -385,10 +433,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.7.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -409,18 +457,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "1.1.0"
|
||||
webview_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_windows
|
||||
sha256: "47fcad5875a45db29dbb5c9e6709bf5c88dcc429049872701343f91ed7255730"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -430,5 +486,5 @@ packages:
|
|||
source: hosted
|
||||
version: "1.0.4"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
dart: ">=3.5.3 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: cdb_ui
|
||||
description: A new Flutter project.
|
||||
description: Economic Database.
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
@ -32,7 +32,7 @@ dependencies:
|
|||
sdk: flutter
|
||||
flutter_simple_treeview: ^3.0.2
|
||||
qr_bar_code: ^1.3.0
|
||||
qr_bar_code_scanner_dialog: ^0.0.5
|
||||
simple_barcode_scanner: ^0.1.2
|
||||
intl: ^0.18.0
|
||||
shared_preferences: ^2.1.0
|
||||
fl_chart: ^0.69.0
|
||||
|
|
|
@ -6,9 +6,15 @@
|
|||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <qr_bar_code/qr_bar_code_plugin_c_api.h>
|
||||
#include <webview_windows/webview_windows_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
QrBarCodePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("QrBarCodePluginCApi"));
|
||||
WebviewWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WebviewWindowsPlugin"));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
permission_handler_windows
|
||||
qr_bar_code
|
||||
webview_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue