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/styledTooltip.scss";
|
||||
@import "css/styledAvatar.scss";
|
||||
@import "./css/styledSelect.scss";
|
||||
@import "./css/styledSelectChild.scss";
|
||||
@import "css/styledSelect.scss";
|
||||
@import "css/styledSelectChild.scss";
|
||||
@import "css/styledButton.scss";
|
||||
@import "css/styledInput.scss";
|
||||
@import "css/styledImageInput.scss";
|
||||
@import "./css/styledModal.scss";
|
||||
@import "css/styledModal.scss";
|
||||
@import "css/styledTextArea.scss";
|
||||
@import "css/styledForm.scss";
|
||||
@import "css/styledEditor.scss";
|
||||
@import "css/styledComment.scss";
|
||||
@import "css/styledPage.scss";
|
||||
@import "./css/styledLink.scss";
|
||||
@import "css/styledLink.scss";
|
||||
@import "css/styledRte.scss";
|
||||
@import "css/styledDateTimeInput.scss";
|
||||
@import "css/app.scss";
|
||||
@import "css/issue.scss";
|
||||
@import "css/project.scss";
|
||||
@import "css/issuesAndFilters.scss";
|
||||
@import "css/projectSettings.scss";
|
||||
@import "css/timeTracking.scss";
|
||||
@import "css/styledCheckbox.scss";
|
||||
|
@ -8,6 +8,7 @@ pub struct StyledLink<'l> {
|
||||
children: Vec<Node<Msg>>,
|
||||
class_list: Vec<&'l str>,
|
||||
href: &'l str,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl<'l> StyledLink<'l> {
|
||||
@ -21,6 +22,7 @@ pub struct StyledLinkBuilder<'l> {
|
||||
children: Vec<Node<Msg>>,
|
||||
class_list: Vec<&'l str>,
|
||||
href: &'l str,
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
impl<'l> StyledLinkBuilder<'l> {
|
||||
@ -44,6 +46,11 @@ impl<'l> StyledLinkBuilder<'l> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self) -> Self {
|
||||
self.disabled = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(self, s: &'l str) -> Self {
|
||||
self.add_child(span![s])
|
||||
}
|
||||
@ -53,6 +60,7 @@ impl<'l> StyledLinkBuilder<'l> {
|
||||
children: self.children,
|
||||
class_list: self.class_list,
|
||||
href: self.href,
|
||||
disabled: self.disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -68,11 +76,14 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
||||
children,
|
||||
class_list,
|
||||
href,
|
||||
disabled,
|
||||
} = values;
|
||||
|
||||
let on_click = {
|
||||
let on_click = if disabled {
|
||||
None
|
||||
} else {
|
||||
let href = href.to_string();
|
||||
mouse_ev("click", move |ev| {
|
||||
Some(mouse_ev("click", move |ev| {
|
||||
if href.starts_with('/') {
|
||||
ev.prevent_default();
|
||||
ev.stop_propagation();
|
||||
@ -82,7 +93,7 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
||||
}
|
||||
|
||||
None as Option<Msg>
|
||||
})
|
||||
}))
|
||||
};
|
||||
|
||||
a![
|
||||
@ -91,6 +102,7 @@ pub fn render(values: StyledLink) -> Node<Msg> {
|
||||
At::Class => class_list.join(" "),
|
||||
At::Href => href,
|
||||
],
|
||||
IF![disabled => attrs![At::OnClick => "return false"]],
|
||||
on_click,
|
||||
children,
|
||||
]
|
||||
|
@ -107,6 +107,9 @@ pub enum Msg {
|
||||
AddIssue,
|
||||
DeleteIssue(EpicId),
|
||||
|
||||
// issues and filters
|
||||
SetActiveIssue(Option<IssueId>),
|
||||
|
||||
// epics
|
||||
AddEpic,
|
||||
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::Profile => pages::profile_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") {
|
||||
log!(model);
|
||||
@ -270,6 +274,7 @@ fn view(model: &model::Model) -> Node<Msg> {
|
||||
Page::Users => pages::users_page::view(model),
|
||||
Page::Profile => pages::profile_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),
|
||||
_ => return None,
|
||||
},
|
||||
"issues-and-filters" => Page::IssuesAndFilters,
|
||||
"add-issue" => Page::AddIssue,
|
||||
"project-settings" => Page::ProjectSettings,
|
||||
"login" => Page::SignIn,
|
||||
|
@ -12,7 +12,7 @@ use {
|
||||
jirs_data::*,
|
||||
seed::{app::Orders, browser::web_socket::WebSocket},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::collections::hash_map::HashMap,
|
||||
std::{borrow::Cow, collections::hash_map::HashMap},
|
||||
uuid::Uuid,
|
||||
};
|
||||
|
||||
@ -83,23 +83,25 @@ pub enum Page {
|
||||
Users,
|
||||
Profile,
|
||||
Reports,
|
||||
IssuesAndFilters,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn to_path(self) -> String {
|
||||
pub fn to_path(self) -> std::borrow::Cow<'static, str> {
|
||||
match self {
|
||||
Page::Project => "/board".to_string(),
|
||||
Page::DeleteEpic(id) => format!("/delete-epic/{id}", id = id),
|
||||
Page::EditEpic(id) => format!("/edit-epic/{id}", id = id),
|
||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
||||
Page::AddIssue => "/add-issue".to_string(),
|
||||
Page::ProjectSettings => "/project-settings".to_string(),
|
||||
Page::SignIn => "/login".to_string(),
|
||||
Page::SignUp => "/register".to_string(),
|
||||
Page::Invite => "/invite".to_string(),
|
||||
Page::Users => "/users".to_string(),
|
||||
Page::Profile => "/profile".to_string(),
|
||||
Page::Reports => "/reports".to_string(),
|
||||
Page::Project => Cow::Borrowed("/board"),
|
||||
Page::DeleteEpic(id) => Cow::Owned(format!("/delete-epic/{id}", id = id)),
|
||||
Page::EditEpic(id) => Cow::Owned(format!("/edit-epic/{id}", id = id)),
|
||||
Page::EditIssue(id) => Cow::Owned(format!("/issues/{id}", id = id)),
|
||||
Page::AddIssue => Cow::Borrowed("/add-issue"),
|
||||
Page::ProjectSettings => Cow::Borrowed("/project-settings"),
|
||||
Page::SignIn => Cow::Borrowed("/login"),
|
||||
Page::SignUp => Cow::Borrowed("/register"),
|
||||
Page::Invite => Cow::Borrowed("/invite"),
|
||||
Page::Users => Cow::Borrowed("/users"),
|
||||
Page::Profile => Cow::Borrowed("/profile"),
|
||||
Page::Reports => Cow::Borrowed("/reports"),
|
||||
Page::IssuesAndFilters => Cow::Borrowed("/issues-and-filters"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,9 +139,12 @@ impl Default for InvitationFormState {
|
||||
#[macro_export]
|
||||
macro_rules! match_page {
|
||||
($model: ident, $ty: ident) => {
|
||||
$crate::match_page!($model, $ty, ())
|
||||
};
|
||||
($model: ident, $ty: ident, $ret: expr) => {
|
||||
match &$model.page_content {
|
||||
PageContent::$ty(page) => page,
|
||||
_ => return,
|
||||
$crate::model::PageContent::$ty(page) => page,
|
||||
_ => return $ret,
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -163,6 +168,7 @@ pub enum PageContent {
|
||||
Users(Box<UsersPage>),
|
||||
Profile(Box<ProfilePage>),
|
||||
Reports(Box<ReportsPage>),
|
||||
IssuesAndFilters(Box<crate::pages::issues_and_filters::Model>),
|
||||
}
|
||||
|
||||
#[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 issues_and_filters;
|
||||
pub mod profile_page;
|
||||
pub mod project_page;
|
||||
pub mod project_settings_page;
|
||||
|
@ -57,7 +57,12 @@ pub fn render(model: &Model) -> Node<Msg> {
|
||||
project_settings(model),
|
||||
li![divider()],
|
||||
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, "Reports", Icon::Reports, Some(Page::Reports)),
|
||||
sidebar_link_item(model, "Components", Icon::Component, None),
|
||||
|
@ -77,7 +77,16 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
||||
vec![]
|
||||
} else {
|
||||
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(
|
||||
"Create Issue",
|
||||
Icon::Plus,
|
||||
|
@ -163,7 +163,10 @@ pub fn update(msg: WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
|
||||
// issue statuses
|
||||
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
|
||||
.issue_statuses
|
||||
.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) => {
|
||||
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
|
||||
.issue_statuses
|
||||
.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) => {
|
||||
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) {
|
||||
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) => {
|
||||
let mut old = vec![];
|
||||
std::mem::swap(&mut model.issue_statuses, &mut old);
|
||||
model.issue_statuses_by_id.remove(&dropped_id);
|
||||
let old = std::mem::replace(&mut model.issue_statuses, vec![]);
|
||||
for is in old {
|
||||
if is.id != dropped_id {
|
||||
model.issue_statuses.push(is);
|
||||
|
@ -1,5 +1,4 @@
|
||||
#![feature(async_closure)]
|
||||
#![feature(vec_remove_item)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use {
|
||||
|
Loading…
Reference in New Issue
Block a user