Starts reports

This commit is contained in:
Adrian Wozniak 2020-05-30 23:45:07 +02:00
parent 9b6bfc76cb
commit 99e39caab7
12 changed files with 167 additions and 24 deletions

2
Cargo.lock generated
View File

@ -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]]

View File

@ -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"

View File

@ -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)

View File

@ -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)]

View File

@ -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> {

View File

@ -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> {

View File

@ -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

View File

@ -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,
);
}

View File

@ -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],
]
}

View File

@ -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),
]);

View File

@ -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"],

View File

@ -171,6 +171,5 @@ pub fn view(model: &Model) -> Node<Msg> {
model,
"users",
vec![form, users_section, invitations_section],
empty![],
)
}