Add delete epic modal
This commit is contained in:
parent
915ff46281
commit
87d2ea48a3
@ -28,21 +28,21 @@ pub struct LoadProjectIssues {
|
|||||||
#[derive(Default, Execute)]
|
#[derive(Default, Execute)]
|
||||||
#[db_exec(result = "Issue", schema = "issues")]
|
#[db_exec(result = "Issue", schema = "issues")]
|
||||||
pub struct UpdateIssue {
|
pub struct UpdateIssue {
|
||||||
pub issue_id: i32,
|
pub issue_id: jirs_data::IssueId,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub issue_type: Option<IssueType>,
|
pub issue_type: Option<IssueType>,
|
||||||
pub priority: Option<IssuePriority>,
|
pub priority: Option<IssuePriority>,
|
||||||
pub list_position: Option<i32>,
|
pub list_position: Option<jirs_data::ListPosition>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub description_text: Option<String>,
|
pub description_text: Option<String>,
|
||||||
pub estimate: Option<i32>,
|
pub estimate: Option<i32>,
|
||||||
pub time_spent: Option<i32>,
|
pub time_spent: Option<i32>,
|
||||||
pub time_remaining: Option<i32>,
|
pub time_remaining: Option<i32>,
|
||||||
pub project_id: Option<i32>,
|
pub project_id: Option<jirs_data::ProjectId>,
|
||||||
pub user_ids: Option<Vec<i32>>,
|
pub user_ids: Option<Vec<jirs_data::UserId>>,
|
||||||
pub reporter_id: Option<i32>,
|
pub reporter_id: Option<jirs_data::UserId>,
|
||||||
pub issue_status_id: Option<i32>,
|
pub issue_status_id: Option<jirs_data::IssueStatusId>,
|
||||||
pub epic_id: Option<Option<i32>>,
|
pub epic_id: Option<Option<jirs_data::EpicId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateIssue {
|
impl UpdateIssue {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use jirs_data::{EpicId, IssueStatusId, ListPosition};
|
|
||||||
use {
|
use {
|
||||||
crate::{WebSocketActor, WsHandler, WsResult},
|
crate::{WebSocketActor, WsHandler, WsResult},
|
||||||
database_actor::{
|
database_actor::{
|
||||||
@ -6,7 +5,10 @@ use {
|
|||||||
issues::{LoadProjectIssues, UpdateIssue},
|
issues::{LoadProjectIssues, UpdateIssue},
|
||||||
},
|
},
|
||||||
futures::executor::block_on,
|
futures::executor::block_on,
|
||||||
jirs_data::{CreateIssuePayload, IssueAssignee, IssueFieldId, IssueId, PayloadVariant, WsMsg},
|
jirs_data::{
|
||||||
|
CreateIssuePayload, IssueAssignee, IssueFieldId, IssueId, IssueStatusId, ListPosition,
|
||||||
|
PayloadVariant, WsMsg,
|
||||||
|
},
|
||||||
std::collections::HashMap,
|
std::collections::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -257,7 +259,7 @@ impl WsHandler<LoadIssues> for WebSocketActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyncIssueListPosition(pub Vec<(IssueId, ListPosition, IssueStatusId, Option<EpicId>)>);
|
pub struct SyncIssueListPosition(pub Vec<(IssueId, ListPosition, IssueStatusId, Option<IssueId>)>);
|
||||||
|
|
||||||
impl WsHandler<SyncIssueListPosition> for WebSocketActor {
|
impl WsHandler<SyncIssueListPosition> for WebSocketActor {
|
||||||
fn handle_msg(&mut self, msg: SyncIssueListPosition, ctx: &mut Self::Context) -> WsResult {
|
fn handle_msg(&mut self, msg: SyncIssueListPosition, ctx: &mut Self::Context) -> WsResult {
|
||||||
|
@ -108,6 +108,62 @@
|
|||||||
&.debugModal {
|
&.debugModal {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.deleteEpic {
|
||||||
|
> section {
|
||||||
|
> .header {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--secondary);
|
||||||
|
|
||||||
|
padding: {
|
||||||
|
top: 15px;
|
||||||
|
right: 40px;
|
||||||
|
left: 40px;
|
||||||
|
bottom: 15px;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
> .warning {
|
||||||
|
margin: {
|
||||||
|
bottom: 10px;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: {
|
||||||
|
right: 40px;
|
||||||
|
left: 40px;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
> .relatedList {
|
||||||
|
list-style: none;
|
||||||
|
padding: {
|
||||||
|
right: 40px;
|
||||||
|
left: 40px;
|
||||||
|
bottom: 20px;
|
||||||
|
};
|
||||||
|
|
||||||
|
> li {
|
||||||
|
> .relatedIssue {
|
||||||
|
list-style: none;
|
||||||
|
line-height: 22px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
color: var(--textLink);
|
||||||
|
|
||||||
|
.styledIcon.link {
|
||||||
|
font-size: 14px;
|
||||||
|
margin: {
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use seed::prelude::WebSocketMessage;
|
use seed::prelude::WebSocketMessage;
|
||||||
|
|
||||||
use jirs_data::{IssueId, IssueStatusId, WsMsg};
|
use jirs_data::{EpicId, IssueStatusId, WsMsg};
|
||||||
|
|
||||||
use crate::shared::styled_editor::Mode as TabMode;
|
use crate::shared::styled_editor::Mode as TabMode;
|
||||||
use crate::FieldId;
|
use crate::FieldId;
|
||||||
@ -16,10 +16,10 @@ pub enum FieldChange {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum BoardPageChange {
|
pub enum BoardPageChange {
|
||||||
// dragging
|
// dragging
|
||||||
IssueDragStarted(IssueId),
|
IssueDragStarted(EpicId),
|
||||||
IssueDragStopped(IssueId),
|
IssueDragStopped(EpicId),
|
||||||
DragLeave(IssueId),
|
DragLeave(EpicId),
|
||||||
ExchangePosition(IssueId),
|
ExchangePosition(EpicId),
|
||||||
IssueDragOverStatus(IssueStatusId),
|
IssueDragOverStatus(IssueStatusId),
|
||||||
IssueDropZone(IssueStatusId),
|
IssueDropZone(IssueStatusId),
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ pub enum Msg {
|
|||||||
|
|
||||||
// issues
|
// issues
|
||||||
AddIssue,
|
AddIssue,
|
||||||
DeleteIssue(IssueId),
|
DeleteIssue(EpicId),
|
||||||
|
|
||||||
// epics
|
// epics
|
||||||
AddEpic,
|
AddEpic,
|
||||||
@ -237,7 +237,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
crate::modals::update(&msg, model, orders);
|
crate::modals::update(&msg, model, orders);
|
||||||
|
|
||||||
match model.page {
|
match model.page {
|
||||||
Page::Project | Page::AddIssue | Page::EditIssue(..) => {
|
Page::Project | Page::AddIssue | Page::EditIssue(..) | Page::DeleteEpic(..) => {
|
||||||
pages::project_page::update(msg, model, orders)
|
pages::project_page::update(msg, model, orders)
|
||||||
}
|
}
|
||||||
Page::ProjectSettings => pages::project_settings_page::update(msg, model, orders),
|
Page::ProjectSettings => pages::project_settings_page::update(msg, model, orders),
|
||||||
@ -255,8 +255,9 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
|
|
||||||
fn view(model: &model::Model) -> Node<Msg> {
|
fn view(model: &model::Model) -> Node<Msg> {
|
||||||
match model.page {
|
match model.page {
|
||||||
Page::Project | Page::AddIssue => pages::project_page::view(model),
|
Page::Project | Page::AddIssue | Page::DeleteEpic(..) | Page::EditIssue(..) => {
|
||||||
Page::EditIssue(_id) => pages::project_page::view(model),
|
pages::project_page::view(model)
|
||||||
|
}
|
||||||
Page::ProjectSettings => pages::project_settings_page::view(model),
|
Page::ProjectSettings => pages::project_settings_page::view(model),
|
||||||
Page::SignIn => pages::sign_in_page::view(model),
|
Page::SignIn => pages::sign_in_page::view(model),
|
||||||
Page::SignUp => pages::sign_up_page::view(model),
|
Page::SignUp => pages::sign_up_page::view(model),
|
||||||
@ -278,6 +279,10 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
Some(Ok(id)) => Page::EditIssue(id),
|
Some(Ok(id)) => Page::EditIssue(id),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
},
|
},
|
||||||
|
"delete-epic" => match url.path().get(1).as_ref().map(|s| s.parse::<i32>()) {
|
||||||
|
Some(Ok(id)) => Page::DeleteEpic(id),
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
"profile" => Page::Profile,
|
"profile" => Page::Profile,
|
||||||
"add-issue" => Page::AddIssue,
|
"add-issue" => Page::AddIssue,
|
||||||
"project-settings" => Page::ProjectSettings,
|
"project-settings" => Page::ProjectSettings,
|
||||||
|
@ -14,7 +14,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
.width(1200)
|
.width(1200)
|
||||||
.add_class("debugModal")
|
.add_class("debugModal")
|
||||||
.center()
|
.center()
|
||||||
.children(vec![code])
|
.children(vec![code].into_iter())
|
||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
5
jirs-client/src/modals/epic_delete/mod.rs
Normal file
5
jirs-client/src/modals/epic_delete/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub use {model::*, update::*, view::*};
|
||||||
|
|
||||||
|
mod model;
|
||||||
|
mod update;
|
||||||
|
mod view;
|
30
jirs-client/src/modals/epic_delete/model.rs
Normal file
30
jirs-client/src/modals/epic_delete/model.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use {
|
||||||
|
crate::model,
|
||||||
|
jirs_data::{EpicId, IssueId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
|
pub struct Model {
|
||||||
|
pub epic_id: EpicId,
|
||||||
|
pub related_issues: Vec<IssueId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn new(issue_id: i32, model: &mut model::Model) -> Self {
|
||||||
|
let related_issues = model
|
||||||
|
.issues
|
||||||
|
.iter()
|
||||||
|
.filter_map(|issue| {
|
||||||
|
if issue.epic_id == Some(issue_id) {
|
||||||
|
Some(issue.id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
epic_id: issue_id,
|
||||||
|
related_issues,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
jirs-client/src/modals/epic_delete/update.rs
Normal file
10
jirs-client/src/modals/epic_delete/update.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use {
|
||||||
|
crate::{shared::go_to_board, Msg},
|
||||||
|
seed::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn update(msg: &Msg, _model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
if let Msg::ModalDropped = msg {
|
||||||
|
go_to_board(orders);
|
||||||
|
};
|
||||||
|
}
|
55
jirs-client/src/modals/epic_delete/view.rs
Normal file
55
jirs-client/src/modals/epic_delete/view.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
modals::epic_delete::Model,
|
||||||
|
model,
|
||||||
|
shared::{styled_confirm_modal::*, styled_icon::*, styled_modal::*, ToNode},
|
||||||
|
Msg,
|
||||||
|
},
|
||||||
|
seed::{prelude::*, *},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn view(model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||||
|
if modal.related_issues.is_empty() {
|
||||||
|
StyledConfirmModal::build()
|
||||||
|
.title("Delete empty epic")
|
||||||
|
.cancel_text("Cancel")
|
||||||
|
.confirm_text("Delete epic")
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
} else {
|
||||||
|
StyledModal::build()
|
||||||
|
.add_class("deleteEpic")
|
||||||
|
.center()
|
||||||
|
.width(600)
|
||||||
|
.child(warning(model, modal))
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
|
||||||
|
let issues: Vec<Node<Msg>> = modal
|
||||||
|
.related_issues
|
||||||
|
.iter()
|
||||||
|
.flat_map(|id| model.issues_by_id.get(id))
|
||||||
|
.map(|issue| {
|
||||||
|
let link = StyledIcon::build(Icon::Link).build().into_node();
|
||||||
|
li![div![
|
||||||
|
C!["relatedIssue"],
|
||||||
|
a![
|
||||||
|
attrs! {"href" => format!("/issues/{}", issue.id)},
|
||||||
|
link,
|
||||||
|
issue.title.as_str()
|
||||||
|
]
|
||||||
|
]]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
section![
|
||||||
|
h3![C!["header"], "Cannot delete epic"],
|
||||||
|
div![
|
||||||
|
C!["warning"],
|
||||||
|
"This epic have related issues. Please move or delete them first."
|
||||||
|
],
|
||||||
|
ol![C!["relatedList"], issues]
|
||||||
|
]
|
||||||
|
}
|
@ -114,7 +114,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.add_class("addIssue")
|
.add_class("addIssue")
|
||||||
.width(0)
|
.width(0)
|
||||||
.variant(crate::shared::styled_modal::Variant::Center)
|
.variant(crate::shared::styled_modal::Variant::Center)
|
||||||
.children(vec![form])
|
.child(form)
|
||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,13 @@ use {
|
|||||||
},
|
},
|
||||||
EditIssueModalSection, FieldId, Msg,
|
EditIssueModalSection, FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{Issue, IssueFieldId, IssueId, TimeTracking, UpdateIssuePayload},
|
jirs_data::{EpicId, Issue, IssueFieldId, TimeTracking, UpdateIssuePayload},
|
||||||
seed::prelude::*,
|
seed::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub id: IssueId,
|
pub id: EpicId,
|
||||||
pub link_copied: bool,
|
pub link_copied: bool,
|
||||||
pub payload: UpdateIssuePayload,
|
pub payload: UpdateIssuePayload,
|
||||||
pub top_type_state: StyledSelectState,
|
pub top_type_state: StyledSelectState,
|
||||||
|
@ -2,6 +2,7 @@ pub use {epic_field::*, update::*, view::*};
|
|||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
|
pub mod epic_delete;
|
||||||
pub mod issue_statuses_delete;
|
pub mod issue_statuses_delete;
|
||||||
pub mod issues_create;
|
pub mod issues_create;
|
||||||
pub mod issues_delete;
|
pub mod issues_delete;
|
||||||
|
@ -12,7 +12,7 @@ use {
|
|||||||
},
|
},
|
||||||
EditIssueModalSection, FieldId, Msg,
|
EditIssueModalSection, FieldId, Msg,
|
||||||
},
|
},
|
||||||
jirs_data::{IssueFieldId, IssueId, TimeTracking},
|
jirs_data::{EpicId, IssueFieldId, TimeTracking},
|
||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ pub fn value_for_time_tracking(v: &Option<i32>, time_tracking_type: &TimeTrackin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model, issue_id: IssueId) -> Node<Msg> {
|
pub fn view(model: &Model, issue_id: EpicId) -> Node<Msg> {
|
||||||
if model.issues_by_id.get(&issue_id).is_none() {
|
if model.issues_by_id.get(&issue_id).is_none() {
|
||||||
return Node::Empty;
|
return Node::Empty;
|
||||||
}
|
}
|
||||||
@ -73,12 +73,7 @@ pub fn view(model: &Model, issue_id: IssueId) -> Node<Msg> {
|
|||||||
|
|
||||||
StyledModal::build()
|
StyledModal::build()
|
||||||
.add_class("timeTrackingModal")
|
.add_class("timeTrackingModal")
|
||||||
.children(vec![
|
.children(vec![modal_title, tracking, inputs, div![C!["actions"], close]].into_iter())
|
||||||
modal_title,
|
|
||||||
tracking,
|
|
||||||
inputs,
|
|
||||||
div![C!["actions"], close],
|
|
||||||
])
|
|
||||||
.width(400)
|
.width(400)
|
||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
|
@ -43,6 +43,9 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
Msg::ChangePage(Page::EditIssue(issue_id)) => push_edit_modal(*issue_id, model, orders),
|
Msg::ChangePage(Page::EditIssue(issue_id)) => push_edit_modal(*issue_id, model, orders),
|
||||||
|
|
||||||
Msg::ChangePage(Page::AddIssue) => push_add_modal(model, orders),
|
Msg::ChangePage(Page::AddIssue) => push_add_modal(model, orders),
|
||||||
|
Msg::ChangePage(Page::DeleteEpic(issue_id)) => {
|
||||||
|
push_delete_epic_modal(*issue_id, model, orders)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
Msg::GlobalKeyDown { key, .. } if key.eq("#") => {
|
||||||
@ -61,10 +64,13 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::modals::{issue_statuses_delete, issues_create, issues_edit};
|
{
|
||||||
issues_create::update(msg, model, orders);
|
use crate::modals::*;
|
||||||
issues_edit::update(msg, model, orders);
|
issues_create::update(msg, model, orders);
|
||||||
issue_statuses_delete::update(msg, model, orders);
|
issues_edit::update(msg, model, orders);
|
||||||
|
issue_statuses_delete::update(msg, model, orders);
|
||||||
|
epic_delete::update(msg, model, orders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_add_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
fn push_add_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
@ -75,12 +81,18 @@ fn push_add_modal(model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
|||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_delete_epic_modal(issue_id: i32, model: &mut Model, _orders: &mut impl Orders<Msg>) {
|
||||||
|
use crate::modals::epic_delete::Model;
|
||||||
|
let modal = Model::new(issue_id, model);
|
||||||
|
model.modals.push(ModalType::DeleteEpic(Box::new(modal)));
|
||||||
|
}
|
||||||
|
|
||||||
fn push_edit_modal(issue_id: i32, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn push_edit_modal(issue_id: i32, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let time_tracking_type = model
|
let time_tracking_type = model
|
||||||
.project
|
.project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| p.time_tracking)
|
.map(|p| p.time_tracking)
|
||||||
.unwrap_or_else(|| TimeTracking::Untracked);
|
.unwrap_or(TimeTracking::Untracked);
|
||||||
let modal = {
|
let modal = {
|
||||||
let issue = match model.issues_by_id.get(&issue_id) {
|
let issue = match model.issues_by_id.get(&issue_id) {
|
||||||
Some(issue) => issue,
|
Some(issue) => issue,
|
||||||
|
@ -18,7 +18,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
StyledModal::build()
|
StyledModal::build()
|
||||||
.variant(crate::shared::styled_modal::Variant::Center)
|
.variant(crate::shared::styled_modal::Variant::Center)
|
||||||
.width(1040)
|
.width(1040)
|
||||||
.children(vec![details])
|
.child(details)
|
||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
} else {
|
} else {
|
||||||
@ -45,6 +45,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
}
|
}
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
ModalType::DebugModal => crate::modals::debug::view(model),
|
ModalType::DebugModal => crate::modals::debug::view(model),
|
||||||
|
ModalType::DeleteEpic(modal) => crate::modals::epic_delete::view(model, modal),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
section![id!["modals"], modals]
|
section![id!["modals"], modals]
|
||||||
|
@ -27,10 +27,11 @@ pub trait IssueModal {
|
|||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum ModalType {
|
pub enum ModalType {
|
||||||
AddIssue(Box<crate::modals::issues_create::Model>),
|
AddIssue(Box<crate::modals::issues_create::Model>),
|
||||||
EditIssue(IssueId, Box<crate::modals::issues_edit::Model>),
|
EditIssue(EpicId, Box<crate::modals::issues_edit::Model>),
|
||||||
DeleteIssueConfirm(IssueId),
|
DeleteEpic(Box<crate::modals::epic_delete::Model>),
|
||||||
|
DeleteIssueConfirm(EpicId),
|
||||||
DeleteCommentConfirm(CommentId),
|
DeleteCommentConfirm(CommentId),
|
||||||
TimeTracking(IssueId),
|
TimeTracking(EpicId),
|
||||||
DeleteIssueStatusModal(Box<crate::modals::issue_statuses_delete::Model>),
|
DeleteIssueStatusModal(Box<crate::modals::issue_statuses_delete::Model>),
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
DebugModal,
|
DebugModal,
|
||||||
@ -46,7 +47,8 @@ pub struct CommentForm {
|
|||||||
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum Page {
|
pub enum Page {
|
||||||
Project,
|
Project,
|
||||||
EditIssue(IssueId),
|
EditIssue(EpicId),
|
||||||
|
DeleteEpic(EpicId),
|
||||||
AddIssue,
|
AddIssue,
|
||||||
ProjectSettings,
|
ProjectSettings,
|
||||||
SignIn,
|
SignIn,
|
||||||
@ -62,6 +64,7 @@ impl Page {
|
|||||||
match self {
|
match self {
|
||||||
Page::Project => "/board".to_string(),
|
Page::Project => "/board".to_string(),
|
||||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
||||||
|
Page::DeleteEpic(id) => format!("/delete-epic/{id}", id = id),
|
||||||
Page::AddIssue => "/add-issue".to_string(),
|
Page::AddIssue => "/add-issue".to_string(),
|
||||||
Page::ProjectSettings => "/project-settings".to_string(),
|
Page::ProjectSettings => "/project-settings".to_string(),
|
||||||
Page::SignIn => "/login".to_string(),
|
Page::SignIn => "/login".to_string(),
|
||||||
@ -147,7 +150,7 @@ pub struct Model {
|
|||||||
pub current_user_project: Option<UserProject>,
|
pub current_user_project: Option<UserProject>,
|
||||||
|
|
||||||
pub issues: Vec<Issue>,
|
pub issues: Vec<Issue>,
|
||||||
pub issues_by_id: HashMap<IssueId, Issue>,
|
pub issues_by_id: HashMap<EpicId, Issue>,
|
||||||
|
|
||||||
pub user: Option<User>,
|
pub user: Option<User>,
|
||||||
pub users: Vec<User>,
|
pub users: Vec<User>,
|
||||||
|
@ -4,12 +4,12 @@ use {crate::shared::drag::DragState, jirs_data::*, std::collections::HashMap};
|
|||||||
pub struct StatusIssueIds {
|
pub struct StatusIssueIds {
|
||||||
pub status_id: IssueStatusId,
|
pub status_id: IssueStatusId,
|
||||||
pub status_name: IssueStatusName,
|
pub status_name: IssueStatusName,
|
||||||
pub issue_ids: Vec<IssueId>,
|
pub issue_ids: Vec<EpicId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct EpicIssuePerStatus {
|
pub struct EpicIssuePerStatus {
|
||||||
pub epic_name: Option<EpicName>,
|
pub epic_ref: Option<(EpicId, EpicName)>,
|
||||||
pub per_status_issues: Vec<StatusIssueIds>,
|
pub per_status_issues: Vec<StatusIssueIds>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ impl ProjectPage {
|
|||||||
|
|
||||||
for epic in epics {
|
for epic in epics {
|
||||||
let mut per_epic_map = EpicIssuePerStatus {
|
let mut per_epic_map = EpicIssuePerStatus {
|
||||||
epic_name: epic.map(|(_, name)| name.to_string()),
|
epic_ref: epic.map(|(id, name)| (id, name.to_string())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::shared::styled_button::StyledButton;
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
model::PageContent,
|
model::PageContent,
|
||||||
@ -9,6 +8,8 @@ use {
|
|||||||
seed::{prelude::*, *},
|
seed::{prelude::*, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::shared::styled_button::StyledButton;
|
||||||
|
|
||||||
pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
||||||
let project_page = match &model.page_content {
|
let project_page = match &model.page_content {
|
||||||
PageContent::Project(project_page) => project_page,
|
PageContent::Project(project_page) => project_page,
|
||||||
@ -32,8 +33,9 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let epic_name = match per_epic.epic_name.as_deref() {
|
let epic_name = match per_epic.epic_ref.as_ref() {
|
||||||
Some(name) => {
|
Some((id, name)) => {
|
||||||
|
let id = *id;
|
||||||
let edit_button = StyledButton::build()
|
let edit_button = StyledButton::build()
|
||||||
.empty()
|
.empty()
|
||||||
.icon(Icon::EditAlt)
|
.icon(Icon::EditAlt)
|
||||||
@ -42,6 +44,15 @@ pub fn project_board_lists(model: &Model) -> Node<Msg> {
|
|||||||
let delete_button = StyledButton::build()
|
let delete_button = StyledButton::build()
|
||||||
.empty()
|
.empty()
|
||||||
.icon(Icon::DeleteAlt)
|
.icon(Icon::DeleteAlt)
|
||||||
|
.on_click(mouse_ev("click", move |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
seed::Url::new()
|
||||||
|
.add_path_part("delete-epic")
|
||||||
|
.add_path_part(id.to_string())
|
||||||
|
.go_and_push();
|
||||||
|
Msg::ChangePage(Page::DeleteEpic(id))
|
||||||
|
}))
|
||||||
.build()
|
.build()
|
||||||
.into_node();
|
.into_node();
|
||||||
|
|
||||||
|
@ -109,15 +109,9 @@ pub fn render(values: StyledConfirmModal) -> Node<Msg> {
|
|||||||
|
|
||||||
StyledModal::build()
|
StyledModal::build()
|
||||||
.width(600)
|
.width(600)
|
||||||
.children(vec![
|
.child(div![C!["title"], title])
|
||||||
div![attrs![At::Class => "title"], title],
|
.child(message_node)
|
||||||
message_node,
|
.child(div![C!["actions"], confirm_button, cancel_button])
|
||||||
div![
|
|
||||||
attrs![At::Class => "actions"],
|
|
||||||
confirm_button,
|
|
||||||
cancel_button
|
|
||||||
],
|
|
||||||
])
|
|
||||||
.add_class("confirmModal")
|
.add_class("confirmModal")
|
||||||
.build()
|
.build()
|
||||||
.into_node()
|
.into_node()
|
||||||
|
@ -58,38 +58,48 @@ pub struct StyledModalBuilder<'l> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledModalBuilder<'l> {
|
impl<'l> StyledModalBuilder<'l> {
|
||||||
|
#[inline]
|
||||||
pub fn variant(mut self, variant: Variant) -> Self {
|
pub fn variant(mut self, variant: Variant) -> Self {
|
||||||
self.variant = Some(variant);
|
self.variant = Some(variant);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn center(self) -> Self {
|
pub fn center(self) -> Self {
|
||||||
self.variant(Variant::Center)
|
self.variant(Variant::Center)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn width(mut self, width: usize) -> Self {
|
pub fn width(mut self, width: usize) -> Self {
|
||||||
self.width = Some(width);
|
self.width = Some(width);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn with_icon(mut self, with_icon: bool) -> Self {
|
#[inline]
|
||||||
// self.with_icon = Some(with_icon);
|
pub fn child(mut self, child: Node<Msg>) -> Self {
|
||||||
// self
|
self.children.get_or_insert(vec![]).push(child);
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn children(mut self, children: Vec<Node<Msg>>) -> Self {
|
|
||||||
self.children = Some(children);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn children<ChildIter>(mut self, children: ChildIter) -> Self
|
||||||
|
where
|
||||||
|
ChildIter: Iterator<Item = Node<Msg>>,
|
||||||
|
{
|
||||||
|
self.children.get_or_insert(vec![]).extend(children);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn add_class(mut self, name: &'l str) -> Self {
|
pub fn add_class(mut self, name: &'l str) -> Self {
|
||||||
self.class_list.push(name);
|
self.class_list.push(name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn build(self) -> StyledModal<'l> {
|
pub fn build(self) -> StyledModal<'l> {
|
||||||
StyledModal {
|
StyledModal {
|
||||||
variant: self.variant.unwrap_or_else(|| Variant::Center),
|
variant: self.variant.unwrap_or(Variant::Center),
|
||||||
width: self.width,
|
width: self.width,
|
||||||
with_icon: self.with_icon.unwrap_or_default(),
|
with_icon: self.with_icon.unwrap_or_default(),
|
||||||
children: self.children.unwrap_or_default(),
|
children: self.children.unwrap_or_default(),
|
||||||
@ -98,6 +108,7 @@ impl<'l> StyledModalBuilder<'l> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn render(values: StyledModal) -> Node<Msg> {
|
pub fn render(values: StyledModal) -> Node<Msg> {
|
||||||
let StyledModal {
|
let StyledModal {
|
||||||
variant,
|
variant,
|
||||||
|
@ -8,7 +8,7 @@ use {
|
|||||||
seed::{prelude::Orders, *},
|
seed::{prelude::Orders, *},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn drag_started(issue_id: IssueId, model: &mut Model) {
|
pub fn drag_started(issue_id: EpicId, model: &mut Model) {
|
||||||
let project_page = match &mut model.page_content {
|
let project_page = match &mut model.page_content {
|
||||||
PageContent::Project(project_page) => project_page,
|
PageContent::Project(project_page) => project_page,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -16,7 +16,7 @@ pub fn drag_started(issue_id: IssueId, model: &mut Model) {
|
|||||||
project_page.issue_drag.drag(issue_id);
|
project_page.issue_drag.drag(issue_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exchange_position(below_id: IssueId, model: &mut Model) {
|
pub fn exchange_position(below_id: EpicId, model: &mut Model) {
|
||||||
let project_page = match &mut model.page_content {
|
let project_page = match &mut model.page_content {
|
||||||
PageContent::Project(project_page) => project_page,
|
PageContent::Project(project_page) => project_page,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -88,7 +88,7 @@ pub fn sync(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let changes: Vec<(IssueId, ListPosition, IssueStatusId, Option<EpicId>)> = dirty
|
let changes: Vec<(EpicId, ListPosition, IssueStatusId, Option<EpicId>)> = dirty
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|id| {
|
.filter_map(|id| {
|
||||||
model.issues_by_id.get(&id).map(|issue| {
|
model.issues_by_id.get(&id).map(|issue| {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#[cfg(feature = "backend")]
|
#[cfg(feature = "backend")]
|
||||||
use diesel::*;
|
use diesel::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "backend")]
|
||||||
|
use derive_enum_sql::EnumSql;
|
||||||
use {
|
use {
|
||||||
chrono::NaiveDateTime,
|
chrono::NaiveDateTime,
|
||||||
derive_enum_iter::EnumIter,
|
derive_enum_iter::EnumIter,
|
||||||
@ -16,9 +18,6 @@ pub mod fields;
|
|||||||
pub mod msg;
|
pub mod msg;
|
||||||
mod payloads;
|
mod payloads;
|
||||||
|
|
||||||
#[cfg(feature = "backend")]
|
|
||||||
use derive_enum_sql::EnumSql;
|
|
||||||
|
|
||||||
pub type NumberOfDeleted = usize;
|
pub type NumberOfDeleted = usize;
|
||||||
pub type IssueId = i32;
|
pub type IssueId = i32;
|
||||||
pub type ListPosition = i32;
|
pub type ListPosition = i32;
|
||||||
@ -223,7 +222,7 @@ pub struct Project {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
pub id: IssueId,
|
pub id: EpicId,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub issue_type: IssueType,
|
pub issue_type: IssueType,
|
||||||
pub priority: IssuePriority,
|
pub priority: IssuePriority,
|
||||||
@ -275,7 +274,7 @@ pub struct Comment {
|
|||||||
pub id: CommentId,
|
pub id: CommentId,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub issue_id: IssueId,
|
pub issue_id: EpicId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
@ -320,7 +319,7 @@ pub struct Token {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct IssueAssignee {
|
pub struct IssueAssignee {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub issue_id: IssueId,
|
pub issue_id: EpicId,
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
pub updated_at: NaiveDateTime,
|
pub updated_at: NaiveDateTime,
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::ListPosition;
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
AvatarUrl, BindToken, Code, Comment, CommentId, CreateCommentPayload, CreateIssuePayload,
|
AvatarUrl, BindToken, Code, Comment, CommentId, CreateCommentPayload, CreateIssuePayload,
|
||||||
EmailString, Epic, EpicId, HighlightedCode, Invitation, InvitationId, InvitationToken,
|
EmailString, Epic, EpicId, HighlightedCode, Invitation, InvitationId, InvitationToken,
|
||||||
Issue, IssueFieldId, IssueId, IssueStatus, IssueStatusId, Lang, Message, MessageId,
|
Issue, IssueFieldId, IssueId, IssueStatus, IssueStatusId, Lang, ListPosition, Message,
|
||||||
NameString, NumberOfDeleted, PayloadVariant, Position, Project, TitleString,
|
MessageId, NameString, NumberOfDeleted, PayloadVariant, Position, Project, TitleString,
|
||||||
UpdateCommentPayload, UpdateProjectPayload, User, UserId, UserProject, UserProjectId,
|
UpdateCommentPayload, UpdateProjectPayload, User, UserId, UserProject, UserProjectId,
|
||||||
UserRole, UsernameString,
|
UserRole, UsernameString,
|
||||||
},
|
},
|
||||||
@ -187,7 +186,7 @@ pub enum WsMsg {
|
|||||||
IssueDeleted(IssueId, NumberOfDeleted),
|
IssueDeleted(IssueId, NumberOfDeleted),
|
||||||
IssueCreate(CreateIssuePayload),
|
IssueCreate(CreateIssuePayload),
|
||||||
IssueCreated(Issue),
|
IssueCreated(Issue),
|
||||||
IssueSyncListPosition(Vec<(IssueId, ListPosition, IssueStatusId, Option<EpicId>)>),
|
IssueSyncListPosition(Vec<(IssueId, ListPosition, IssueStatusId, Option<IssueId>)>),
|
||||||
|
|
||||||
// issue status
|
// issue status
|
||||||
IssueStatusesLoad,
|
IssueStatusesLoad,
|
||||||
|
Loading…
Reference in New Issue
Block a user