Add issues and filters
This commit is contained in:
parent
1c666dc26a
commit
adea6d4b4a
135
jirs-client/js/css/issuesAndFilters.scss
Normal file
135
jirs-client/js/css/issuesAndFilters.scss
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#issuesAndFilters {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
--listWidth: 338px;
|
||||||
|
|
||||||
|
> .issueInfo {
|
||||||
|
width: calc(100% - var(--listWidth));
|
||||||
|
padding: {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
top: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
};
|
||||||
|
|
||||||
|
> .header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "icon link" "icon name";
|
||||||
|
grid-template-columns: 48px;
|
||||||
|
|
||||||
|
> .logo {
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .path {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> .styledLink {
|
||||||
|
color: var(--textLink);
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: var(--textLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .issueBody {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> .details {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
> .line {
|
||||||
|
--lineWidth: 460px;
|
||||||
|
--nameWidth: 150px;
|
||||||
|
|
||||||
|
list-style: none;
|
||||||
|
width: var(--lineWidth);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: {
|
||||||
|
top: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 5px;
|
||||||
|
right: 5px;
|
||||||
|
};
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
> .detailsTitle {
|
||||||
|
color: var(--textLight);
|
||||||
|
width: var(--nameWidth);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .detailsValue {
|
||||||
|
width: calc(var(--lineWidth) - var(--nameWidth));
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .issuesList {
|
||||||
|
width: var(--listWidth);
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
> .listItem {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
> .issue {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "type number" "priority name";
|
||||||
|
grid-template-columns: 32px auto;
|
||||||
|
border: {
|
||||||
|
bottom: 1px solid var(--borderLight);
|
||||||
|
}
|
||||||
|
padding: {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
top: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
};
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--backgroundLightPrimary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--backgroundLightest);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--backgroundLightPrimary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .type {
|
||||||
|
grid-area: type;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .number {
|
||||||
|
grid-area: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .priority {
|
||||||
|
grid-area: priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .name {
|
||||||
|
grid-area: name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,23 +9,24 @@
|
|||||||
@import "css/shared.scss";
|
@import "css/shared.scss";
|
||||||
@import "css/styledTooltip.scss";
|
@import "css/styledTooltip.scss";
|
||||||
@import "css/styledAvatar.scss";
|
@import "css/styledAvatar.scss";
|
||||||
@import "./css/styledSelect.scss";
|
@import "css/styledSelect.scss";
|
||||||
@import "./css/styledSelectChild.scss";
|
@import "css/styledSelectChild.scss";
|
||||||
@import "css/styledButton.scss";
|
@import "css/styledButton.scss";
|
||||||
@import "css/styledInput.scss";
|
@import "css/styledInput.scss";
|
||||||
@import "css/styledImageInput.scss";
|
@import "css/styledImageInput.scss";
|
||||||
@import "./css/styledModal.scss";
|
@import "css/styledModal.scss";
|
||||||
@import "css/styledTextArea.scss";
|
@import "css/styledTextArea.scss";
|
||||||
@import "css/styledForm.scss";
|
@import "css/styledForm.scss";
|
||||||
@import "css/styledEditor.scss";
|
@import "css/styledEditor.scss";
|
||||||
@import "css/styledComment.scss";
|
@import "css/styledComment.scss";
|
||||||
@import "css/styledPage.scss";
|
@import "css/styledPage.scss";
|
||||||
@import "./css/styledLink.scss";
|
@import "css/styledLink.scss";
|
||||||
@import "css/styledRte.scss";
|
@import "css/styledRte.scss";
|
||||||
@import "css/styledDateTimeInput.scss";
|
@import "css/styledDateTimeInput.scss";
|
||||||
@import "css/app.scss";
|
@import "css/app.scss";
|
||||||
@import "css/issue.scss";
|
@import "css/issue.scss";
|
||||||
@import "css/project.scss";
|
@import "css/project.scss";
|
||||||
|
@import "css/issuesAndFilters.scss";
|
||||||
@import "css/projectSettings.scss";
|
@import "css/projectSettings.scss";
|
||||||
@import "css/timeTracking.scss";
|
@import "css/timeTracking.scss";
|
||||||
@import "css/styledCheckbox.scss";
|
@import "css/styledCheckbox.scss";
|
||||||
|
@ -8,6 +8,7 @@ pub struct StyledLink<'l> {
|
|||||||
children: Vec<Node<Msg>>,
|
children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
class_list: Vec<&'l str>,
|
||||||
href: &'l str,
|
href: &'l str,
|
||||||
|
disabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledLink<'l> {
|
impl<'l> StyledLink<'l> {
|
||||||
@ -21,6 +22,7 @@ pub struct StyledLinkBuilder<'l> {
|
|||||||
children: Vec<Node<Msg>>,
|
children: Vec<Node<Msg>>,
|
||||||
class_list: Vec<&'l str>,
|
class_list: Vec<&'l str>,
|
||||||
href: &'l str,
|
href: &'l str,
|
||||||
|
disabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledLinkBuilder<'l> {
|
impl<'l> StyledLinkBuilder<'l> {
|
||||||
@ -44,6 +46,11 @@ impl<'l> StyledLinkBuilder<'l> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn disabled(mut self) -> Self {
|
||||||
|
self.disabled = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text(self, s: &'l str) -> Self {
|
pub fn text(self, s: &'l str) -> Self {
|
||||||
self.add_child(span![s])
|
self.add_child(span![s])
|
||||||
}
|
}
|
||||||
@ -53,6 +60,7 @@ impl<'l> StyledLinkBuilder<'l> {
|
|||||||
children: self.children,
|
children: self.children,
|
||||||
class_list: self.class_list,
|
class_list: self.class_list,
|
||||||
href: self.href,
|
href: self.href,
|
||||||
|
disabled: self.disabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,11 +76,14 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
|||||||
children,
|
children,
|
||||||
class_list,
|
class_list,
|
||||||
href,
|
href,
|
||||||
|
disabled,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let on_click = {
|
let on_click = if disabled {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
let href = href.to_string();
|
let href = href.to_string();
|
||||||
mouse_ev("click", move |ev| {
|
Some(mouse_ev("click", move |ev| {
|
||||||
if href.starts_with('/') {
|
if href.starts_with('/') {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
@ -82,7 +93,7 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
None as Option<Msg>
|
None as Option<Msg>
|
||||||
})
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
a![
|
a![
|
||||||
@ -91,6 +102,7 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
|||||||
At::Class => class_list.join(" "),
|
At::Class => class_list.join(" "),
|
||||||
At::Href => href,
|
At::Href => href,
|
||||||
],
|
],
|
||||||
|
IF![disabled => attrs![At::OnClick => "return false"]],
|
||||||
on_click,
|
on_click,
|
||||||
children,
|
children,
|
||||||
]
|
]
|
||||||
|
@ -107,6 +107,9 @@ pub enum Msg {
|
|||||||
AddIssue,
|
AddIssue,
|
||||||
DeleteIssue(EpicId),
|
DeleteIssue(EpicId),
|
||||||
|
|
||||||
|
// issues and filters
|
||||||
|
SetActiveIssue(Option<IssueId>),
|
||||||
|
|
||||||
// epics
|
// epics
|
||||||
AddEpic,
|
AddEpic,
|
||||||
DeleteEpic,
|
DeleteEpic,
|
||||||
@ -250,6 +253,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Page::Users => pages::users_page::update(msg, model, orders),
|
Page::Users => pages::users_page::update(msg, model, orders),
|
||||||
Page::Profile => pages::profile_page::update(msg, model, orders),
|
Page::Profile => pages::profile_page::update(msg, model, orders),
|
||||||
Page::Reports => pages::reports_page::update(msg, model, orders),
|
Page::Reports => pages::reports_page::update(msg, model, orders),
|
||||||
|
Page::IssuesAndFilters => pages::issues_and_filters::update(msg, model, orders),
|
||||||
}
|
}
|
||||||
if cfg!(features = "print-model") {
|
if cfg!(features = "print-model") {
|
||||||
log!(model);
|
log!(model);
|
||||||
@ -270,6 +274,7 @@ fn view(model: &model::Model) -> Node<Msg> {
|
|||||||
Page::Users => pages::users_page::view(model),
|
Page::Users => pages::users_page::view(model),
|
||||||
Page::Profile => pages::profile_page::view(model),
|
Page::Profile => pages::profile_page::view(model),
|
||||||
Page::Reports => pages::reports_page::view(model),
|
Page::Reports => pages::reports_page::view(model),
|
||||||
|
Page::IssuesAndFilters => pages::issues_and_filters::view(model),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +290,7 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
Some(Ok(id)) => Page::EditIssue(id),
|
Some(Ok(id)) => Page::EditIssue(id),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
|
"issues-and-filters" => Page::IssuesAndFilters,
|
||||||
"add-issue" => Page::AddIssue,
|
"add-issue" => Page::AddIssue,
|
||||||
"project-settings" => Page::ProjectSettings,
|
"project-settings" => Page::ProjectSettings,
|
||||||
"login" => Page::SignIn,
|
"login" => Page::SignIn,
|
||||||
|
@ -12,7 +12,7 @@ use {
|
|||||||
jirs_data::*,
|
jirs_data::*,
|
||||||
seed::{app::Orders, browser::web_socket::WebSocket},
|
seed::{app::Orders, browser::web_socket::WebSocket},
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
std::collections::hash_map::HashMap,
|
std::{borrow::Cow, collections::hash_map::HashMap},
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,23 +83,25 @@ pub enum Page {
|
|||||||
Users,
|
Users,
|
||||||
Profile,
|
Profile,
|
||||||
Reports,
|
Reports,
|
||||||
|
IssuesAndFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
pub fn to_path(self) -> String {
|
pub fn to_path(self) -> std::borrow::Cow<'static, str> {
|
||||||
match self {
|
match self {
|
||||||
Page::Project => "/board".to_string(),
|
Page::Project => Cow::Borrowed("/board"),
|
||||||
Page::DeleteEpic(id) => format!("/delete-epic/{id}", id = id),
|
Page::DeleteEpic(id) => Cow::Owned(format!("/delete-epic/{id}", id = id)),
|
||||||
Page::EditEpic(id) => format!("/edit-epic/{id}", id = id),
|
Page::EditEpic(id) => Cow::Owned(format!("/edit-epic/{id}", id = id)),
|
||||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
Page::EditIssue(id) => Cow::Owned(format!("/issues/{id}", id = id)),
|
||||||
Page::AddIssue => "/add-issue".to_string(),
|
Page::AddIssue => Cow::Borrowed("/add-issue"),
|
||||||
Page::ProjectSettings => "/project-settings".to_string(),
|
Page::ProjectSettings => Cow::Borrowed("/project-settings"),
|
||||||
Page::SignIn => "/login".to_string(),
|
Page::SignIn => Cow::Borrowed("/login"),
|
||||||
Page::SignUp => "/register".to_string(),
|
Page::SignUp => Cow::Borrowed("/register"),
|
||||||
Page::Invite => "/invite".to_string(),
|
Page::Invite => Cow::Borrowed("/invite"),
|
||||||
Page::Users => "/users".to_string(),
|
Page::Users => Cow::Borrowed("/users"),
|
||||||
Page::Profile => "/profile".to_string(),
|
Page::Profile => Cow::Borrowed("/profile"),
|
||||||
Page::Reports => "/reports".to_string(),
|
Page::Reports => Cow::Borrowed("/reports"),
|
||||||
|
Page::IssuesAndFilters => Cow::Borrowed("/issues-and-filters"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,9 +139,12 @@ impl Default for InvitationFormState {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! match_page {
|
macro_rules! match_page {
|
||||||
($model: ident, $ty: ident) => {
|
($model: ident, $ty: ident) => {
|
||||||
|
$crate::match_page!($model, $ty, ())
|
||||||
|
};
|
||||||
|
($model: ident, $ty: ident, $ret: expr) => {
|
||||||
match &$model.page_content {
|
match &$model.page_content {
|
||||||
PageContent::$ty(page) => page,
|
$crate::model::PageContent::$ty(page) => page,
|
||||||
_ => return,
|
_ => return $ret,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -163,6 +168,7 @@ pub enum PageContent {
|
|||||||
Users(Box<UsersPage>),
|
Users(Box<UsersPage>),
|
||||||
Profile(Box<ProfilePage>),
|
Profile(Box<ProfilePage>),
|
||||||
Reports(Box<ReportsPage>),
|
Reports(Box<ReportsPage>),
|
||||||
|
IssuesAndFilters(Box<crate::pages::issues_and_filters::Model>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
5
jirs-client/src/pages/issues_and_filters/mod.rs
Normal file
5
jirs-client/src/pages/issues_and_filters/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub use {model::*, update::*, view::*};
|
||||||
|
|
||||||
|
mod model;
|
||||||
|
mod update;
|
||||||
|
mod view;
|
26
jirs-client/src/pages/issues_and_filters/model.rs
Normal file
26
jirs-client/src/pages/issues_and_filters/model.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use {
|
||||||
|
crate::model,
|
||||||
|
jirs_data::{Issue, IssueId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Model {
|
||||||
|
pub visible_issues: Vec<IssueId>,
|
||||||
|
pub active_id: Option<IssueId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(model: &model::Model) -> Self {
|
||||||
|
let visible_issues = Self::visible_issues(model.issues());
|
||||||
|
let active_id = model.issues().first().as_ref().map(|issue| issue.id);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
visible_issues,
|
||||||
|
active_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible_issues(issues: &[Issue]) -> Vec<IssueId> {
|
||||||
|
issues.iter().map(|issue| issue.id).collect()
|
||||||
|
}
|
||||||
|
}
|
45
jirs-client/src/pages/issues_and_filters/update.rs
Normal file
45
jirs-client/src/pages/issues_and_filters/update.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
model::{Model, PageContent},
|
||||||
|
ws::board_load,
|
||||||
|
Msg, OperationKind, Page, ResourceKind,
|
||||||
|
},
|
||||||
|
seed::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
match msg {
|
||||||
|
Msg::ResourceChanged(ResourceKind::Auth, OperationKind::SingleLoaded, Some(_))
|
||||||
|
| Msg::ChangePage(Page::IssuesAndFilters) => {
|
||||||
|
board_load(model, orders);
|
||||||
|
build_page_content(model);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
|
||||||
|
let issues = super::Model::visible_issues(model.issues());
|
||||||
|
let first_id = model.issues().first().as_ref().map(|issue| issue.id);
|
||||||
|
let page = crate::match_page_mut!(model, IssuesAndFilters);
|
||||||
|
if page.active_id.is_none() {
|
||||||
|
page.active_id = first_id;
|
||||||
|
}
|
||||||
|
page.visible_issues = issues;
|
||||||
|
}
|
||||||
|
Msg::SetActiveIssue(Some(id)) => {
|
||||||
|
let page = crate::match_page_mut!(model, IssuesAndFilters);
|
||||||
|
page.active_id = Some(id);
|
||||||
|
}
|
||||||
|
Msg::SetActiveIssue(None) => {
|
||||||
|
let first_id = model.issues().first().as_ref().map(|issue| issue.id);
|
||||||
|
let page = crate::match_page_mut!(model, IssuesAndFilters);
|
||||||
|
page.active_id = first_id;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_page_content(model: &mut Model) {
|
||||||
|
model.page_content = PageContent::IssuesAndFilters(Box::new(super::Model::new(model)));
|
||||||
|
}
|
30
jirs-client/src/pages/issues_and_filters/view.rs
Normal file
30
jirs-client/src/pages/issues_and_filters/view.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
mod filters;
|
||||||
|
mod issue_info;
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{model::Model, shared::inner_layout, Msg},
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let page = crate::match_page!(model, IssuesAndFilters, Node::Empty);
|
||||||
|
let project_name = model
|
||||||
|
.project
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.name.as_str())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let details = match page.active_id.and_then(|id| model.issues_by_id.get(&id)) {
|
||||||
|
Some(issue) => issue_info::issue_details(model, issue, project_name),
|
||||||
|
_ => Node::Empty,
|
||||||
|
};
|
||||||
|
let content = div![
|
||||||
|
C!["container"],
|
||||||
|
issue_info::issues_list(model, page, project_name),
|
||||||
|
details
|
||||||
|
];
|
||||||
|
inner_layout(
|
||||||
|
model,
|
||||||
|
"issuesAndFilters",
|
||||||
|
&[filters::filters(model, page), content],
|
||||||
|
)
|
||||||
|
}
|
8
jirs-client/src/pages/issues_and_filters/view/filters.rs
Normal file
8
jirs-client/src/pages/issues_and_filters/view/filters.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use {
|
||||||
|
crate::{model::Model, Msg},
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn filters(_model: &Model, _page: &super::super::Model) -> Node<Msg> {
|
||||||
|
div![C!["filters"]]
|
||||||
|
}
|
160
jirs-client/src/pages/issues_and_filters/view/issue_info.rs
Normal file
160
jirs-client/src/pages/issues_and_filters/view/issue_info.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
components::{styled_icon::*, styled_link::*},
|
||||||
|
model::Model,
|
||||||
|
shared::ToNode,
|
||||||
|
Msg,
|
||||||
|
},
|
||||||
|
jirs_data::{Issue, IssueId},
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn issue_details(model: &Model, issue: &Issue, project_name: &str) -> Node<Msg> {
|
||||||
|
let Issue {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
issue_type,
|
||||||
|
priority,
|
||||||
|
description,
|
||||||
|
issue_status_id,
|
||||||
|
epic_id,
|
||||||
|
created_at: _,
|
||||||
|
updated_at: _,
|
||||||
|
user_ids: _,
|
||||||
|
..
|
||||||
|
} = issue;
|
||||||
|
|
||||||
|
let issue_link = StyledLink::build()
|
||||||
|
.href(format!("/issues/{}", id).as_str())
|
||||||
|
.text(format!("{}-{}", project_name, id).as_str())
|
||||||
|
.disabled()
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let project_link = StyledLink::build()
|
||||||
|
.disabled()
|
||||||
|
.href("/board")
|
||||||
|
.text(
|
||||||
|
model
|
||||||
|
.project
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.name.as_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
|
||||||
|
let details = {
|
||||||
|
let issue_type = {
|
||||||
|
let type_name = issue_type.to_string();
|
||||||
|
let type_icon = Icon::from(*issue_type).into_node();
|
||||||
|
li![
|
||||||
|
C!["line"],
|
||||||
|
div![C!["detailsTitle"], "Type:"],
|
||||||
|
div![C!["type detailsValue"], type_icon, type_name],
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let priority = {
|
||||||
|
let name = priority.to_string();
|
||||||
|
let icon = Icon::from(*priority).into_node();
|
||||||
|
li![
|
||||||
|
C!["line"],
|
||||||
|
div![C!["detailsTitle"], "Priority:"],
|
||||||
|
div![C!["priority detailsValue"], icon, name],
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let epic = li![
|
||||||
|
C!["line"],
|
||||||
|
div![C!["detailsTitle"], "Epic link:"],
|
||||||
|
match epic_id.and_then(|id| model.epics_by_id.get(&id)) {
|
||||||
|
Some(epic) => div![C!["detailsValue epic"], a![epic.name.as_str()]],
|
||||||
|
_ => div![C!["detailsValue epic"], "None"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let status = li![
|
||||||
|
C!["line"],
|
||||||
|
div![C!["detailsTitle"], "Status:"],
|
||||||
|
div![C!["detailsValue status"], {
|
||||||
|
match model.issue_statuses_by_id.get(issue_status_id) {
|
||||||
|
Some(status) => status.name.as_str(),
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
ul![C!["details"], issue_type, priority, epic, status]
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_column = div![];
|
||||||
|
|
||||||
|
div![
|
||||||
|
C!["issueInfo"],
|
||||||
|
div![
|
||||||
|
C!["header"],
|
||||||
|
div![C!["logo"], "X"],
|
||||||
|
div![C!["title"], title.as_str()],
|
||||||
|
div![C!["path"], project_link, "/", issue_link]
|
||||||
|
],
|
||||||
|
div![C!["issueBody"], details, right_column],
|
||||||
|
div![
|
||||||
|
C!["description"],
|
||||||
|
raw! { description.as_deref().unwrap_or_default() }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn issues_list(model: &Model, page: &super::super::Model, project_name: &str) -> Node<Msg> {
|
||||||
|
let issues: Vec<Node<Msg>> = page
|
||||||
|
.visible_issues
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| model.issues_by_id.get(id))
|
||||||
|
.map(|issue| issue_entry(page, issue, project_name))
|
||||||
|
.collect();
|
||||||
|
ul![C!["issuesList"], issues]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn issue_entry(page: &super::super::Model, issue: &Issue, project_name: &str) -> Node<Msg> {
|
||||||
|
let link = issue_link(issue.id, project_name);
|
||||||
|
let ty = {
|
||||||
|
let icon: Icon = issue.issue_type.into();
|
||||||
|
icon.into_node()
|
||||||
|
};
|
||||||
|
let priority = {
|
||||||
|
let icon: Icon = issue.priority.into();
|
||||||
|
icon.into_node()
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_click = {
|
||||||
|
let id = issue.id;
|
||||||
|
mouse_ev("click", move |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::SetActiveIssue(Some(id))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
li![
|
||||||
|
C!["listItem"],
|
||||||
|
on_click,
|
||||||
|
a![
|
||||||
|
C!["issue"],
|
||||||
|
IF![page.active_id == Some(issue.id) => C!["active"]],
|
||||||
|
div![C!["number"], link],
|
||||||
|
div![C!["name"], issue.title.as_str()],
|
||||||
|
div![C!["type"], ty],
|
||||||
|
div![C!["priority"], priority]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn issue_link(id: IssueId, project_name: &str) -> Node<Msg> {
|
||||||
|
StyledLink::build()
|
||||||
|
.with_icon()
|
||||||
|
.href(format!("/issues/{}", id).as_str())
|
||||||
|
.text(format!("{}-{}", project_name, id).as_str())
|
||||||
|
.disabled()
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod invite_page;
|
pub mod invite_page;
|
||||||
|
pub mod issues_and_filters;
|
||||||
pub mod profile_page;
|
pub mod profile_page;
|
||||||
pub mod project_page;
|
pub mod project_page;
|
||||||
pub mod project_settings_page;
|
pub mod project_settings_page;
|
||||||
|
@ -57,7 +57,12 @@ pub fn render(model: &Model) -> Node<Msg> {
|
|||||||
project_settings(model),
|
project_settings(model),
|
||||||
li![divider()],
|
li![divider()],
|
||||||
sidebar_link_item(model, "Releases", Icon::Shipping, None),
|
sidebar_link_item(model, "Releases", Icon::Shipping, None),
|
||||||
sidebar_link_item(model, "Issue and Filters", Icon::Issues, None),
|
sidebar_link_item(
|
||||||
|
model,
|
||||||
|
"Issue and Filters",
|
||||||
|
Icon::Issues,
|
||||||
|
Some(Page::IssuesAndFilters)
|
||||||
|
),
|
||||||
sidebar_link_item(model, "Pages", Icon::Page, None),
|
sidebar_link_item(model, "Pages", Icon::Page, None),
|
||||||
sidebar_link_item(model, "Reports", Icon::Reports, Some(Page::Reports)),
|
sidebar_link_item(model, "Reports", Icon::Reports, Some(Page::Reports)),
|
||||||
sidebar_link_item(model, "Components", Icon::Component, None),
|
sidebar_link_item(model, "Components", Icon::Component, None),
|
||||||
|
@ -77,7 +77,16 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
navbar_left_item("Search issues", Icon::Search, None, None),
|
navbar_left_item(
|
||||||
|
"Search issues",
|
||||||
|
Icon::Search,
|
||||||
|
Some("/issues-and-filters"),
|
||||||
|
Some(mouse_ev("click", |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::ChangePage(Page::IssuesAndFilters)
|
||||||
|
})),
|
||||||
|
),
|
||||||
navbar_left_item(
|
navbar_left_item(
|
||||||
"Create Issue",
|
"Create Issue",
|
||||||
Icon::Plus,
|
Icon::Plus,
|
||||||
|
@ -163,7 +163,10 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
|
|
||||||
// issue statuses
|
// issue statuses
|
||||||
WsMsg::IssueStatusesLoaded(v) => {
|
WsMsg::IssueStatusesLoaded(v) => {
|
||||||
model.issue_statuses = v;
|
model.issue_statuses = v.clone();
|
||||||
|
for is in v {
|
||||||
|
model.issue_statuses_by_id.insert(is.id, is);
|
||||||
|
}
|
||||||
model
|
model
|
||||||
.issue_statuses
|
.issue_statuses
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
.sort_by(|a, b| a.position.cmp(&b.position));
|
||||||
@ -175,7 +178,8 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
WsMsg::IssueStatusCreated(is) => {
|
WsMsg::IssueStatusCreated(is) => {
|
||||||
let id = is.id;
|
let id = is.id;
|
||||||
model.issue_statuses.push(is);
|
model.issue_statuses.push(is.clone());
|
||||||
|
model.issue_statuses_by_id.insert(is.id, is);
|
||||||
model
|
model
|
||||||
.issue_statuses
|
.issue_statuses
|
||||||
.sort_by(|a, b| a.position.cmp(&b.position));
|
.sort_by(|a, b| a.position.cmp(&b.position));
|
||||||
@ -187,6 +191,9 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
WsMsg::IssueStatusUpdated(mut changed) => {
|
WsMsg::IssueStatusUpdated(mut changed) => {
|
||||||
let id = changed.id;
|
let id = changed.id;
|
||||||
|
model
|
||||||
|
.issue_statuses_by_id
|
||||||
|
.insert(changed.id, changed.clone());
|
||||||
if let Some(idx) = model.issue_statuses.iter().position(|c| c.id == changed.id) {
|
if let Some(idx) = model.issue_statuses.iter().position(|c| c.id == changed.id) {
|
||||||
std::mem::swap(&mut model.issue_statuses[idx], &mut changed);
|
std::mem::swap(&mut model.issue_statuses[idx], &mut changed);
|
||||||
}
|
}
|
||||||
@ -200,8 +207,8 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
WsMsg::IssueStatusDeleted(dropped_id, _count) => {
|
WsMsg::IssueStatusDeleted(dropped_id, _count) => {
|
||||||
let mut old = vec![];
|
model.issue_statuses_by_id.remove(&dropped_id);
|
||||||
std::mem::swap(&mut model.issue_statuses, &mut old);
|
let old = std::mem::replace(&mut model.issue_statuses, vec![]);
|
||||||
for is in old {
|
for is in old {
|
||||||
if is.id != dropped_id {
|
if is.id != dropped_id {
|
||||||
model.issue_statuses.push(is);
|
model.issue_statuses.push(is);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#![feature(async_closure)]
|
#![feature(async_closure)]
|
||||||
#![feature(vec_remove_item)]
|
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
Loading…
Reference in New Issue
Block a user