work
This commit is contained in:
parent
b3a96ed3e3
commit
ca24591d9d
12 changed files with 177 additions and 105 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,3 +5,5 @@
|
|||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
/assets/tailwind.css
|
||||
|
|
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -378,6 +378,7 @@ dependencies = [
|
|||
"bardecoder",
|
||||
"base64",
|
||||
"dioxus",
|
||||
"dioxus-material-icons",
|
||||
"dioxus-sdk",
|
||||
"gloo-timers",
|
||||
"image",
|
||||
|
@ -1194,6 +1195,15 @@ dependencies = [
|
|||
"tracing-wasm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-material-icons"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf38d89d3100bb3dbe38c7c08eef9d87fb81fcec49a4e33ead63fdcf4e67f01"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dioxus-mobile"
|
||||
version = "0.6.2"
|
||||
|
|
|
@ -21,6 +21,7 @@ serde_json = "1.0.140"
|
|||
dioxus-sdk = { version = "0.6.0", features = ["storage"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
reqwest = { version = "0.12.15", features = ["json"] }
|
||||
dioxus-material-icons = "3.0.0"
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
|
|
|
@ -2,8 +2,6 @@ FROM git.hydrar.de/navos/navos:latest as builder
|
|||
|
||||
RUN pacman-key --init && pacman-key --populate archlinux && pacman-key --populate navos && pacman -Sy --noconfirm
|
||||
|
||||
RUN pacman -S --noconfirm tailwindcss-bin rustup
|
||||
|
||||
RUN pacman -S --noconfirm --needed \
|
||||
webkit2gtk-4.1 \
|
||||
base-devel \
|
||||
|
@ -14,9 +12,9 @@ RUN pacman -S --noconfirm --needed \
|
|||
appmenu-gtk-module \
|
||||
libappindicator-gtk3 \
|
||||
librsvg \
|
||||
xdotool
|
||||
xdotool tailwindcss-bin rustup && pacman -Scc --noconfirm
|
||||
|
||||
RUN rustup default nightly
|
||||
RUN rustup default nightly && rustup target add wasm32-unknown-unknown
|
||||
|
||||
RUN cargo install dioxus-cli
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
81
src/api.rs
81
src/api.rs
|
@ -1,76 +1,3 @@
|
|||
/*
|
||||
import 'dart:convert';
|
||||
import 'package:cdb_ui/pages/supply.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
// todo : api errors
|
||||
|
||||
class API {
|
||||
SharedPreferences? pref;
|
||||
static final API _instance = API._internal();
|
||||
|
||||
// cache
|
||||
List<Item>? items;
|
||||
Map<String, Location>? locations;
|
||||
Map<String, FlowInfo>? flowInfos;
|
||||
|
||||
factory API() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
API._internal();
|
||||
|
||||
Future<void> init(Function refresh) async {
|
||||
pref = await SharedPreferences.getInstance();
|
||||
instance = pref!.getString("instance") ?? "";
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool isInit() {
|
||||
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);
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
String instance = "";
|
||||
|
||||
Future<String> getRequest(String url) async {
|
||||
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")!
|
||||
});
|
||||
|
||||
return utf8.decode(resp.bodyBytes);
|
||||
}
|
||||
|
||||
Future<String> postRequest(String url, Map<String, dynamic> data) async {
|
||||
var resp = await http.post(Uri.parse(url),
|
||||
headers: <String, String>{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
'Token': pref!.getString("token")!
|
||||
},
|
||||
body: jsonEncode(data));
|
||||
|
||||
return utf8.decode(resp.bodyBytes);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dioxus::signals::Readable;
|
||||
|
@ -84,6 +11,14 @@ use reqwest::Client;
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn get_item(item: &str) -> Option<Item> {
|
||||
crate::API
|
||||
.read()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get_item(item.to_string())
|
||||
}
|
||||
|
||||
pub async fn api_get_auth<T>(path: String) -> Result<T, reqwest::Error>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -1,5 +1,6 @@
|
|||
use api::{Item, Transaction};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_material_icons::{MaterialIcon, MaterialIconStylesheet};
|
||||
use dioxus_sdk::storage::use_persistent;
|
||||
use qrscan::QRCodeScanPage;
|
||||
use setup::{Credentials, SetupPage};
|
||||
|
@ -69,6 +70,7 @@ fn App() -> Element {
|
|||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
|
||||
MaterialIconStylesheet { }
|
||||
|
||||
if creds.read().empty() {
|
||||
SetupPage { }
|
||||
|
@ -144,21 +146,37 @@ fn Navbar() -> Element {
|
|||
id: "navbar",
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
div {
|
||||
class: "flex flex-row gap-2 p-2",
|
||||
MaterialIcon { name: "home", size: 24 },
|
||||
"Home"
|
||||
}
|
||||
}
|
||||
Link {
|
||||
to: Route::ItemPage { },
|
||||
div {
|
||||
class: "flex flex-row gap-2 p-2",
|
||||
MaterialIcon { name: "category", size: 24 },
|
||||
"Items"
|
||||
}
|
||||
}
|
||||
Link {
|
||||
to: Route::FlowPage { },
|
||||
div {
|
||||
class: "flex flex-row gap-2 p-2",
|
||||
MaterialIcon { name: "assignment", size: 24 },
|
||||
"Flows"
|
||||
}
|
||||
}
|
||||
Link {
|
||||
to: Route::LocationsPage { },
|
||||
div {
|
||||
class: "flex flex-row gap-2 p-2",
|
||||
MaterialIcon { name: "map", size: 24 },
|
||||
"Locations"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn ItemDetailPage(id: String) -> Element {
|
|||
class: "flex flex-col h-screen",
|
||||
|
||||
header {
|
||||
class: "p-4 bg-blue-500 text-white text-lg font-bold",
|
||||
class: "p-4 text-white text-lg font-bold",
|
||||
{item.name.as_str()}
|
||||
}
|
||||
div {
|
||||
|
@ -79,9 +79,9 @@ pub fn ItemDetailPage(id: String) -> Element {
|
|||
Route::SupplyPage {
|
||||
item: item.uuid.clone(),
|
||||
param: SupplyPageParam {
|
||||
onlyVariants: None,
|
||||
forcePrice: None,
|
||||
forceOrigin: None
|
||||
only_variants: None,
|
||||
force_price: None,
|
||||
force_origin: None
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -19,10 +19,13 @@ pub fn ItemPage() -> Element {
|
|||
match &*items.read_unchecked() {
|
||||
Some(items) => {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col",
|
||||
for item in items {
|
||||
ItemTile { item: item.clone() }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => { rsx! { p { "Loading" }}}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,99 @@
|
|||
use dioxus::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn build_tree(root: &str, locs: &HashMap<String, Location>) -> Element {
|
||||
rsx! {}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn LocationsPage() -> Element {
|
||||
let api = crate::API.read();
|
||||
let locations = use_signal(|| api.as_ref().unwrap().get_locations().clone());
|
||||
|
||||
// TODO : find roots
|
||||
// build them
|
||||
let roots: Vec<String> = get_root_locations(&*locations.read());
|
||||
|
||||
rsx! {
|
||||
h1 { "Locations" },
|
||||
|
||||
for l in &roots {
|
||||
LocationTreeTile { location: l.clone(), locations }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_root_locations(locations: &HashMap<String, crate::api::Location>) -> Vec<String> {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for (_, v) in locations {
|
||||
if v.parent.is_none() {
|
||||
ret.push(v.id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn get_children_of_loc(
|
||||
loc: String,
|
||||
locations: &HashMap<String, crate::api::Location>,
|
||||
) -> Vec<String> {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
for (_, v) in locations {
|
||||
if let Some(parent) = &v.parent {
|
||||
if *parent == loc {
|
||||
ret.push(v.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn LocationTreeTile(
|
||||
location: String,
|
||||
locations: Signal<HashMap<String, crate::api::Location>>,
|
||||
) -> Element {
|
||||
let mut open = use_signal(|| false);
|
||||
|
||||
// TODO
|
||||
let children = get_children_of_loc(location.clone(), &*locations.read());
|
||||
|
||||
rsx! {
|
||||
p { {location} }
|
||||
button {
|
||||
onclick: move |_| { open.set(true); },
|
||||
"Expand"
|
||||
}
|
||||
|
||||
if *open.read() {
|
||||
for loc in &children {
|
||||
LocationTreeTile { location: loc.clone(), locations }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn LocationPage(location: String) -> Element {
|
||||
let mut recursive = use_signal(|| true);
|
||||
|
||||
rsx! {
|
||||
h1 { {location} }
|
||||
|
||||
div {
|
||||
id: "location-info",
|
||||
p { "Info" }
|
||||
}
|
||||
|
||||
input {
|
||||
r#type: "checkbox",
|
||||
value: !*recursive.read(),
|
||||
onclick: move |_| {
|
||||
let val = *recursive.read();
|
||||
recursive.set(!val);
|
||||
},
|
||||
"Show only entries matching this location"
|
||||
}
|
||||
|
||||
// Transaction List
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
|
|||
|
||||
let origin = use_signal(|| String::new());
|
||||
|
||||
let mut price = use_signal(|| 0.0);
|
||||
let mut price = use_signal(|| param.force_price.unwrap_or(0.0));
|
||||
let location = use_signal(|| String::new());
|
||||
let mut note = use_signal(|| String::new());
|
||||
|
||||
|
@ -72,7 +72,14 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
|
|||
input {
|
||||
r#type: "number",
|
||||
value: "{price}",
|
||||
oninput: move |e| price.set(e.value().parse().unwrap())
|
||||
disabled: param.force_price.is_some(),
|
||||
oninput: move |e| {
|
||||
if let Some(fprice) = param.force_price {
|
||||
price.set(fprice);
|
||||
} else {
|
||||
price.set(e.value().parse().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Location Dropdown
|
||||
|
@ -107,9 +114,9 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
|
|||
|
||||
#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct SupplyPageParam {
|
||||
pub onlyVariants: Option<Vec<String>>,
|
||||
pub forcePrice: Option<f64>,
|
||||
pub forceOrigin: Option<String>,
|
||||
pub only_variants: Option<Vec<String>>,
|
||||
pub force_price: Option<f64>,
|
||||
pub force_origin: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SupplyPageParam {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_material_icons::MaterialIcon;
|
||||
|
||||
use crate::Route;
|
||||
use crate::{api::get_item, Route};
|
||||
|
||||
#[component]
|
||||
pub fn TransactionPage(id: String) -> Element {
|
||||
|
@ -10,7 +11,6 @@ pub fn TransactionPage(id: String) -> Element {
|
|||
});
|
||||
|
||||
rsx! {
|
||||
|
||||
match transaction.value()() {
|
||||
Some(transaction) => rsx! {
|
||||
|
||||
|
@ -32,9 +32,9 @@ pub fn TransactionPage(id: String) -> Element {
|
|||
div {
|
||||
id: "col-1",
|
||||
|
||||
p { {crate::API.read().as_ref().unwrap().get_item(transaction.item.clone()).unwrap().name.as_str()} }
|
||||
// TODO : variant name
|
||||
// timestamp
|
||||
p { {get_item(&transaction.item).unwrap().name.as_str()} }
|
||||
p { {get_item(&transaction.item).unwrap().variants.get(&transaction.variant).as_ref().unwrap().name.clone()} }
|
||||
p { {transaction.timestamp.to_string()} }
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -45,6 +45,9 @@ pub fn TransactionPage(id: String) -> Element {
|
|||
// price
|
||||
p { {transaction.price.to_string()} },
|
||||
// origin
|
||||
if let Some(origin) = &transaction.origin {
|
||||
IconLabel { icon: "home".to_string(), label: "Origin".to_owned(), value: origin.clone() }
|
||||
}
|
||||
// location
|
||||
// note
|
||||
|
||||
|
@ -58,3 +61,20 @@ pub fn TransactionPage(id: String) -> Element {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn IconLabel(icon: String, label: String, value: String) -> Element {
|
||||
rsx! {
|
||||
div { class: "flex items-center space-x-3",
|
||||
MaterialIcon {
|
||||
name: icon,
|
||||
size: 12,
|
||||
},
|
||||
|
||||
div { class: "flex flex-col",
|
||||
span { class: "text-sm font-medium text-gray-700", "{label}" }
|
||||
span { class: "text-lg font-semibold text-gray-900", "{value}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue