Starts reports
This commit is contained in:
parent
9b6bfc76cb
commit
99e39caab7
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -667,10 +667,12 @@ version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.43",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -23,7 +23,7 @@ seed = { version = "0.7.0" }
|
||||
serde = "*"
|
||||
serde_json = "*"
|
||||
bincode = "1.2.1"
|
||||
chrono = { version = "0.4", features = [ "serde" ] }
|
||||
chrono = { version = "0.4", features = [ "serde", "wasmbind" ] }
|
||||
uuid = { version = "0.8.1", features = [ "serde" ] }
|
||||
wasm-bindgen = "0.2.60"
|
||||
futures = "^0.1.26"
|
||||
|
@ -19,6 +19,7 @@ mod model;
|
||||
mod profile;
|
||||
mod project;
|
||||
mod project_settings;
|
||||
mod reports;
|
||||
mod shared;
|
||||
mod sign_in;
|
||||
mod sign_up;
|
||||
@ -193,6 +194,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||
Page::Invite => invite::update(msg, model, orders),
|
||||
Page::Users => users::update(msg, model, orders),
|
||||
Page::Profile => profile::update(msg, model, orders),
|
||||
Page::Reports => reports::update(msg, model, orders),
|
||||
}
|
||||
if cfg!(debug_assertions) {
|
||||
// debug!(model);
|
||||
@ -209,6 +211,7 @@ fn view(model: &model::Model) -> Node<Msg> {
|
||||
Page::Invite => invite::view(model),
|
||||
Page::Users => users::view(model),
|
||||
Page::Profile => profile::view(model),
|
||||
Page::Reports => reports::view(model),
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +240,7 @@ fn resolve_page(url: Url) -> Option<Page> {
|
||||
"register" => Page::SignUp,
|
||||
"invite" => Page::Invite,
|
||||
"users" => Page::Users,
|
||||
"reports" => Page::Reports,
|
||||
_ => Page::Project,
|
||||
};
|
||||
Some(page)
|
||||
|
@ -215,6 +215,7 @@ pub enum Page {
|
||||
Invite,
|
||||
Users,
|
||||
Profile,
|
||||
Reports,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
@ -229,6 +230,7 @@ impl Page {
|
||||
Page::Invite => "/invite".to_string(),
|
||||
Page::Users => "/users".to_string(),
|
||||
Page::Profile => "/profile".to_string(),
|
||||
Page::Reports => "/reports".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,6 +430,9 @@ impl ProfilePage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReportsPage {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PageContent {
|
||||
SignIn(Box<SignInPage>),
|
||||
@ -437,6 +442,7 @@ pub enum PageContent {
|
||||
Invite(Box<InvitePage>),
|
||||
Users(Box<UsersPage>),
|
||||
Profile(Box<ProfilePage>),
|
||||
Reports(Box<ReportsPage>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use jirs_data::*;
|
||||
@ -11,7 +13,6 @@ use crate::shared::styled_input::StyledInput;
|
||||
use crate::shared::styled_select::StyledSelect;
|
||||
use crate::shared::{inner_layout, ToChild, ToNode};
|
||||
use crate::{FieldId, Msg, PageChanged, ProfilePageChange};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
let page = match &model.page_content {
|
||||
@ -75,7 +76,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
.add_field(submit_field)
|
||||
.build()
|
||||
.into_node();
|
||||
inner_layout(model, "profile", vec![content], crate::modal::view(model))
|
||||
inner_layout(model, "profile", vec![content])
|
||||
}
|
||||
|
||||
fn build_current_project(model: &Model, page: &Box<ProfilePage>) -> Node<Msg> {
|
||||
|
@ -19,12 +19,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
project_board_lists(model),
|
||||
];
|
||||
|
||||
inner_layout(
|
||||
model,
|
||||
"projectPage",
|
||||
project_section,
|
||||
crate::modal::view(model),
|
||||
)
|
||||
inner_layout(model, "projectPage", project_section)
|
||||
}
|
||||
|
||||
fn breadcrumbs(model: &Model) -> Node<Msg> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
use seed::{prelude::*, *};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use jirs_data::{IssueStatus, ProjectCategory, TimeTracking, ToVec};
|
||||
|
||||
use crate::model::{DeleteIssueStatusModal, ModalType, Model, PageContent, ProjectSettingsPage};
|
||||
@ -88,12 +89,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
|
||||
let project_section = vec![div![class!["formContainer"], form]];
|
||||
|
||||
inner_layout(
|
||||
model,
|
||||
"projectSettings",
|
||||
project_section,
|
||||
crate::modal::view(model),
|
||||
)
|
||||
inner_layout(model, "projectSettings", project_section)
|
||||
}
|
||||
|
||||
/// Build project name input with styled field wrapper
|
||||
|
@ -0,0 +1,49 @@
|
||||
use seed::prelude::*;
|
||||
|
||||
use jirs_data::WsMsg;
|
||||
|
||||
use crate::model::{Model, Page, PageContent, ReportsPage};
|
||||
use crate::ws::enqueue_ws_msg;
|
||||
use crate::{Msg, WebSocketChanged};
|
||||
|
||||
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::ChangePage(Page::Reports) => {
|
||||
build_page_content(model);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let _page = match &mut model.page_content {
|
||||
PageContent::Reports(page) => page,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if model.user.is_none() {
|
||||
return;
|
||||
}
|
||||
match msg {
|
||||
Msg::UserChanged(Some(..))
|
||||
| Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)))
|
||||
| Msg::ChangePage(Page::Reports) => {
|
||||
init_load(model, orders);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_page_content(model: &mut Model) {
|
||||
model.page_content = PageContent::Reports(Box::new(ReportsPage {}))
|
||||
}
|
||||
|
||||
fn init_load(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
if model.user.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
enqueue_ws_msg(
|
||||
vec![WsMsg::ProjectIssuesRequest, WsMsg::IssueStatusesRequest],
|
||||
model.ws.as_ref(),
|
||||
orders,
|
||||
);
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::Datelike;
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use jirs_data::Issue;
|
||||
|
||||
use crate::model::Model;
|
||||
use crate::shared::inner_layout;
|
||||
use crate::Msg;
|
||||
|
||||
const SVG_MARGIN_X: u32 = 5;
|
||||
const SVG_DRAWABLE_HEIGHT: u32 = 300;
|
||||
const SVG_HEIGHT: u32 = SVG_DRAWABLE_HEIGHT + 30;
|
||||
const SVG_WIDTH: u32 = 1060;
|
||||
const SVG_BAR_WIDTH: u32 = 25;
|
||||
const SVG_BAR_MARGIN: u32 = 10;
|
||||
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
let body = section![
|
||||
h1![class!["header"], "Reports"],
|
||||
div![this_month_graph(model)],
|
||||
];
|
||||
|
||||
inner_layout(model, "reports", vec![body])
|
||||
}
|
||||
|
||||
fn this_month_graph(model: &Model) -> Node<Msg> {
|
||||
let first_day = chrono::Utc::today().with_day(1).unwrap().naive_local();
|
||||
let last_day = (first_day + chrono::Duration::days(32))
|
||||
.with_day(1)
|
||||
.unwrap()
|
||||
- chrono::Duration::days(1);
|
||||
|
||||
let this_month_updated: Vec<&Issue> = model
|
||||
.issues
|
||||
.iter()
|
||||
.filter(|issue| issue.updated_at.date() >= first_day)
|
||||
.collect();
|
||||
|
||||
let mut issues: HashMap<u32, Vec<&Issue>> = HashMap::new();
|
||||
|
||||
let list: Vec<Node<Msg>> = this_month_updated
|
||||
.into_iter()
|
||||
.map(|issue| {
|
||||
let date = issue.updated_at.date();
|
||||
issues.entry(date.day0()).or_default().push(issue);
|
||||
let day = issue.updated_at.date().format("%Y-%m-%d").to_string();
|
||||
li![span![issue.title.as_str()], span![day.as_str()]]
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut columns: Vec<Node<Msg>> = vec![];
|
||||
let x_origin: Node<Msg> = seed::rect![attrs![
|
||||
At::X => SVG_MARGIN_X,
|
||||
At::Y => SVG_HEIGHT - SVG_MARGIN_X - 20,
|
||||
At::Width => SVG_WIDTH - (SVG_MARGIN_X * 2),
|
||||
At::Height => 2,
|
||||
At::Style => "fill: var(--textDark);"
|
||||
]];
|
||||
|
||||
for day in (first_day.day0())..(last_day.day0()) {
|
||||
let x = (SVG_BAR_WIDTH * day) + (SVG_BAR_MARGIN * day) + (SVG_MARGIN_X * 2);
|
||||
let num_issues = issues.get(&day).map(|v| v.len()).unwrap_or_default() as u32;
|
||||
let height = num_issues * SVG_BAR_WIDTH;
|
||||
|
||||
let day = first_day.with_day0(day).unwrap();
|
||||
|
||||
columns.push(seed::rect![attrs![
|
||||
At::X => x,
|
||||
At::Y => SVG_DRAWABLE_HEIGHT - height, // reverse draw origin
|
||||
At::Width => "10",
|
||||
At::Height => height,
|
||||
At::Style => "fill: rgb(255,0,0);",
|
||||
At::Title => format!("Number of issues: {}", num_issues),
|
||||
]]);
|
||||
columns.push(seed::text![
|
||||
attrs![
|
||||
At::X => x,
|
||||
At::Y => SVG_HEIGHT,
|
||||
At::Style => "fill: var(--textDark); font-family: var(--font-regular); font-size: 10px;",
|
||||
],
|
||||
day.format("%d/%m").to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
div![
|
||||
svg![
|
||||
attrs![At::Height => SVG_HEIGHT, At::Width => SVG_WIDTH],
|
||||
x_origin,
|
||||
columns,
|
||||
],
|
||||
ul![list],
|
||||
]
|
||||
}
|
@ -63,7 +63,7 @@ pub fn render(model: &Model) -> Node<Msg> {
|
||||
sidebar_link_item(model, "Releases", Icon::Shipping, None),
|
||||
sidebar_link_item(model, "Issue and Filters", Icon::Issues, None),
|
||||
sidebar_link_item(model, "Pages", Icon::Page, None),
|
||||
sidebar_link_item(model, "Reports", Icon::Reports, None),
|
||||
sidebar_link_item(model, "Reports", Icon::Reports, Some(Page::Reports)),
|
||||
sidebar_link_item(model, "Components", Icon::Component, None),
|
||||
]);
|
||||
|
||||
|
@ -61,12 +61,8 @@ pub fn divider() -> Node<Msg> {
|
||||
div![class!["divider"], ""]
|
||||
}
|
||||
|
||||
pub fn inner_layout(
|
||||
model: &Model,
|
||||
page_name: &str,
|
||||
children: Vec<Node<Msg>>,
|
||||
modal_node: Node<Msg>,
|
||||
) -> Node<Msg> {
|
||||
pub fn inner_layout(model: &Model, page_name: &str, children: Vec<Node<Msg>>) -> Node<Msg> {
|
||||
let modal_node = crate::modal::view(model);
|
||||
article![
|
||||
modal_node,
|
||||
class!["inner-layout", "innerPage"],
|
||||
|
@ -171,6 +171,5 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
model,
|
||||
"users",
|
||||
vec![form, users_section, invitations_section],
|
||||
empty![],
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user