Set epic as issue group
This commit is contained in:
parent
316d315bea
commit
331acd4574
13
README.md
13
README.md
@ -42,6 +42,19 @@ https://git.sr.ht/~tsumanu/jirs
|
|||||||
* Add personal settings to choose MDE (Markdown Editor) or RTE
|
* Add personal settings to choose MDE (Markdown Editor) or RTE
|
||||||
* Add issues and filters
|
* Add issues and filters
|
||||||
|
|
||||||
|
##### Work Progress
|
||||||
|
|
||||||
|
* [X] Add Epic
|
||||||
|
* [ ] Edit Epic
|
||||||
|
* [ ] Delete Epic
|
||||||
|
* [ ] Epic `starts` and `ends` date
|
||||||
|
* [X] Grouping by Epic
|
||||||
|
* [X] Basic Rich Text Editor
|
||||||
|
* [ ] Insert Code in Rich Text Editor
|
||||||
|
* [ ] Personal settings to choose MDE (Markdown Editor) or RTE
|
||||||
|
* [ ] Issues and filters view
|
||||||
|
* [ ] Issues and filters working filters
|
||||||
|
|
||||||
## How to run it
|
## How to run it
|
||||||
|
|
||||||
### Config files
|
### Config files
|
||||||
|
46
jirs-client/src/modal/issues.rs
Normal file
46
jirs-client/src/modal/issues.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use seed::prelude::Node;
|
||||||
|
|
||||||
|
use jirs_data::EpicId;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
model::{IssueModal, Model},
|
||||||
|
shared::{styled_field::StyledField, styled_select::StyledSelect, ToChild, ToNode},
|
||||||
|
FieldId, Msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod add_issue;
|
||||||
|
pub mod issue_details;
|
||||||
|
|
||||||
|
pub fn epic_field<Modal>(model: &Model, modal: &Modal, field_id: FieldId) -> Option<Node<Msg>>
|
||||||
|
where
|
||||||
|
Modal: IssueModal,
|
||||||
|
{
|
||||||
|
if model.epics.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let selected = modal
|
||||||
|
.epic_id_value()
|
||||||
|
.and_then(|id| model.epics.iter().find(|epic| epic.id == id as EpicId))
|
||||||
|
.map(|epic| vec![epic.to_child()])
|
||||||
|
.unwrap_or_default();
|
||||||
|
let input = StyledSelect::build(field_id)
|
||||||
|
.name("epic")
|
||||||
|
.selected(selected)
|
||||||
|
.options(model.epics.iter().map(|epic| epic.to_child()).collect())
|
||||||
|
.normal()
|
||||||
|
.clearable()
|
||||||
|
.text_filter(modal.epic_state().text_filter.as_str())
|
||||||
|
.opened(modal.epic_state().opened)
|
||||||
|
.valid(true)
|
||||||
|
.build()
|
||||||
|
.into_node();
|
||||||
|
Some(
|
||||||
|
StyledField::build()
|
||||||
|
.label("Epic")
|
||||||
|
.tip("Feature group")
|
||||||
|
.input(input)
|
||||||
|
.build()
|
||||||
|
.into_node(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,25 @@
|
|||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
|
|
||||||
use jirs_data::{EpicId, IssueFieldId, WsMsg};
|
use jirs_data::{IssueFieldId, IssuePriority, ToVec, UserId, WsMsg};
|
||||||
use jirs_data::{IssuePriority, ToVec};
|
|
||||||
|
|
||||||
use crate::model::{AddIssueModal, ModalType, Model};
|
use crate::{
|
||||||
use crate::shared::styled_button::StyledButton;
|
modal::issues::epic_field,
|
||||||
use crate::shared::styled_field::StyledField;
|
model::{AddIssueModal, IssueModal, ModalType, Model},
|
||||||
use crate::shared::styled_form::StyledForm;
|
shared::{
|
||||||
use crate::shared::styled_input::StyledInput;
|
styled_button::StyledButton,
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
styled_field::StyledField,
|
||||||
use crate::shared::styled_select::StyledSelect;
|
styled_form::StyledForm,
|
||||||
use crate::shared::styled_select::StyledSelectChange;
|
styled_input::StyledInput,
|
||||||
use crate::shared::styled_select_child::{StyledSelectChild, StyledSelectChildBuilder};
|
styled_modal::{StyledModal, Variant as ModalVariant},
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
styled_select::StyledSelect,
|
||||||
use crate::shared::{ToChild, ToNode};
|
styled_select::StyledSelectChange,
|
||||||
use crate::ws::send_ws_msg;
|
styled_select_child::{StyledSelectChild, StyledSelectChildBuilder},
|
||||||
use crate::{FieldId, Msg, WebSocketChanged};
|
styled_textarea::StyledTextarea,
|
||||||
|
ToChild, ToNode,
|
||||||
|
},
|
||||||
|
ws::send_ws_msg,
|
||||||
|
FieldId, Msg, WebSocketChanged,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
enum Type {
|
enum Type {
|
||||||
@ -114,12 +118,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
modal.title_state.update(msg);
|
modal.update_states(msg, orders);
|
||||||
modal.assignees_state.update(msg, orders);
|
|
||||||
modal.reporter_state.update(msg, orders);
|
|
||||||
modal.type_state.update(msg, orders);
|
|
||||||
modal.priority_state.update(msg, orders);
|
|
||||||
modal.epic_state.update(msg, orders);
|
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::AddEpic => {
|
Msg::AddEpic => {
|
||||||
@ -182,15 +181,15 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
FieldId::AddIssueModal(IssueFieldId::Reporter),
|
FieldId::AddIssueModal(IssueFieldId::Reporter),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(id),
|
||||||
) => {
|
) => {
|
||||||
modal.reporter_id = Some(*id as i32);
|
modal.reporter_id = id.map(|n| n as UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssigneesAddIssueModal
|
// AssigneesAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AddIssueModal(IssueFieldId::Assignees),
|
FieldId::AddIssueModal(IssueFieldId::Assignees),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(Some(id)),
|
||||||
) => {
|
) => {
|
||||||
let id = *id as i32;
|
let id = *id as UserId;
|
||||||
if !modal.user_ids.contains(&id) {
|
if !modal.user_ids.contains(&id) {
|
||||||
modal.user_ids.push(id);
|
modal.user_ids.push(id);
|
||||||
}
|
}
|
||||||
@ -212,7 +211,7 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
// IssuePriorityAddIssueModal
|
// IssuePriorityAddIssueModal
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::AddIssueModal(IssueFieldId::Priority),
|
FieldId::AddIssueModal(IssueFieldId::Priority),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(Some(id)),
|
||||||
) => {
|
) => {
|
||||||
modal.priority = (*id).into();
|
modal.priority = (*id).into();
|
||||||
}
|
}
|
||||||
@ -247,7 +246,7 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
let reporter_field = reporter_field(model, modal);
|
let reporter_field = reporter_field(model, modal);
|
||||||
let assignees_field = assignees_field(model, modal);
|
let assignees_field = assignees_field(model, modal);
|
||||||
let issue_priority_field = issue_priority_field(modal);
|
let issue_priority_field = issue_priority_field(modal);
|
||||||
let epic_field = epic_field(model, modal);
|
let epic_field = epic_field(model, modal, FieldId::AddIssueModal(IssueFieldId::Epic));
|
||||||
|
|
||||||
form.add_field(short_summary_field)
|
form.add_field(short_summary_field)
|
||||||
.add_field(description_field)
|
.add_field(description_field)
|
||||||
@ -456,38 +455,6 @@ fn issue_priority_field(modal: &AddIssueModal) -> Node<Msg> {
|
|||||||
.into_node()
|
.into_node()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn epic_field(model: &Model, modal: &AddIssueModal) -> Option<Node<Msg>> {
|
|
||||||
if model.epics.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let selected = modal
|
|
||||||
.epic_state
|
|
||||||
.values
|
|
||||||
.get(0)
|
|
||||||
.and_then(|id| model.epics.iter().find(|epic| epic.id == *id as EpicId))
|
|
||||||
.map(|epic| vec![epic.to_child()])
|
|
||||||
.unwrap_or_default();
|
|
||||||
let input = StyledSelect::build(FieldId::AddIssueModal(IssueFieldId::Epic))
|
|
||||||
.name("epic")
|
|
||||||
.selected(selected)
|
|
||||||
.options(model.epics.iter().map(|epic| epic.to_child()).collect())
|
|
||||||
.normal()
|
|
||||||
.text_filter(modal.epic_state.text_filter.as_str())
|
|
||||||
.opened(modal.epic_state.opened)
|
|
||||||
.valid(true)
|
|
||||||
.build()
|
|
||||||
.into_node();
|
|
||||||
Some(
|
|
||||||
StyledField::build()
|
|
||||||
.label("Epic")
|
|
||||||
.tip("Feature group")
|
|
||||||
.input(input)
|
|
||||||
.build()
|
|
||||||
.into_node(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name_field(modal: &AddIssueModal) -> Node<Msg> {
|
fn name_field(modal: &AddIssueModal) -> Node<Msg> {
|
||||||
let name = StyledInput::build(FieldId::AddIssueModal(IssueFieldId::Title))
|
let name = StyledInput::build(FieldId::AddIssueModal(IssueFieldId::Title))
|
||||||
.state(&modal.title_state)
|
.state(&modal.title_state)
|
@ -2,37 +2,31 @@ use seed::{prelude::*, *};
|
|||||||
|
|
||||||
use jirs_data::*;
|
use jirs_data::*;
|
||||||
|
|
||||||
use crate::modal::time_tracking::time_tracking_field;
|
use crate::{
|
||||||
use crate::model::{CommentForm, EditIssueModal, ModalType, Model};
|
modal::{issues::epic_field, time_tracking::time_tracking_field},
|
||||||
use crate::shared::styled_avatar::StyledAvatar;
|
model::{CommentForm, EditIssueModal, IssueModal, ModalType, Model},
|
||||||
use crate::shared::styled_button::StyledButton;
|
shared::{
|
||||||
use crate::shared::styled_editor::StyledEditor;
|
styled_avatar::StyledAvatar,
|
||||||
use crate::shared::styled_field::StyledField;
|
styled_button::StyledButton,
|
||||||
use crate::shared::styled_icon::Icon;
|
styled_editor::StyledEditor,
|
||||||
use crate::shared::styled_input::StyledInput;
|
styled_field::StyledField,
|
||||||
use crate::shared::styled_select::{StyledSelect, StyledSelectChange};
|
styled_icon::Icon,
|
||||||
use crate::shared::styled_textarea::StyledTextarea;
|
styled_input::StyledInput,
|
||||||
use crate::shared::tracking_widget::tracking_link;
|
styled_select::{StyledSelect, StyledSelectChange},
|
||||||
use crate::shared::{ToChild, ToNode};
|
styled_textarea::StyledTextarea,
|
||||||
use crate::ws::send_ws_msg;
|
tracking_widget::tracking_link,
|
||||||
use crate::{EditIssueModalSection, FieldChange, FieldId, Msg, WebSocketChanged};
|
ToChild, ToNode,
|
||||||
|
},
|
||||||
|
ws::send_ws_msg,
|
||||||
|
EditIssueModalSection, FieldChange, FieldId, Msg, WebSocketChanged,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
let modal: &mut EditIssueModal = match model.modals.get_mut(0) {
|
||||||
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
Some(ModalType::EditIssue(_issue_id, modal)) => modal,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
modal.top_type_state.update(msg, orders);
|
modal.update_states(msg, orders);
|
||||||
modal.status_state.update(msg, orders);
|
|
||||||
modal.reporter_state.update(msg, orders);
|
|
||||||
modal.assignees_state.update(msg, orders);
|
|
||||||
modal.priority_state.update(msg, orders);
|
|
||||||
modal.estimate.update(msg);
|
|
||||||
modal.estimate_select.update(msg, orders);
|
|
||||||
modal.time_spent.update(msg);
|
|
||||||
modal.time_spent_select.update(msg, orders);
|
|
||||||
modal.time_remaining.update(msg);
|
|
||||||
modal.time_remaining_select.update(msg, orders);
|
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueUpdated(issue))) => {
|
Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::IssueUpdated(issue))) => {
|
||||||
@ -40,7 +34,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Type)),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.issue_type = (*value).into();
|
modal.payload.issue_type = (*value).into();
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -55,7 +49,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::IssueStatusId)),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.issue_status_id = *value as IssueStatusId;
|
modal.payload.issue_status_id = *value as IssueStatusId;
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -70,7 +64,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Reporter)),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.reporter_id = *value as i32;
|
modal.payload.reporter_id = *value as i32;
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -85,7 +79,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Assignees)),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.user_ids.push(*value as i32);
|
modal.payload.user_ids.push(*value as i32);
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -122,7 +116,7 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
modal.payload.priority = (*value).into();
|
modal.payload.priority = (*value).into();
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
@ -267,6 +261,20 @@ pub fn update(msg: &Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Msg::StyledSelectChanged(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Epic)),
|
||||||
|
StyledSelectChange::Changed(v),
|
||||||
|
) => {
|
||||||
|
send_ws_msg(
|
||||||
|
WsMsg::IssueUpdate(
|
||||||
|
modal.id,
|
||||||
|
IssueFieldId::Epic,
|
||||||
|
PayloadVariant::OptionI32(v.map(|n| n as EpicId).clone()),
|
||||||
|
),
|
||||||
|
model.ws.as_ref(),
|
||||||
|
orders,
|
||||||
|
);
|
||||||
|
}
|
||||||
Msg::ModalChanged(FieldChange::TabChanged(
|
Msg::ModalChanged(FieldChange::TabChanged(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Description)),
|
||||||
mode,
|
mode,
|
||||||
@ -792,6 +800,13 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
(empty![], empty![])
|
(empty![], empty![])
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let epic_field = epic_field(
|
||||||
|
model,
|
||||||
|
modal,
|
||||||
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Epic)),
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|| empty![]);
|
||||||
|
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "right"],
|
attrs![At::Class => "right"],
|
||||||
status_field,
|
status_field,
|
||||||
@ -800,5 +815,6 @@ fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
priority_field,
|
priority_field,
|
||||||
estimate_field,
|
estimate_field,
|
||||||
tracking_field,
|
tracking_field,
|
||||||
|
epic_field,
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -2,19 +2,24 @@ use seed::{prelude::*, *};
|
|||||||
|
|
||||||
use jirs_data::{TimeTracking, WsMsg};
|
use jirs_data::{TimeTracking, WsMsg};
|
||||||
|
|
||||||
use crate::model::{AddIssueModal, EditIssueModal, ModalType, Model, Page};
|
use crate::{
|
||||||
use crate::shared::styled_confirm_modal::StyledConfirmModal;
|
modal::issues::*,
|
||||||
use crate::shared::styled_modal::{StyledModal, Variant as ModalVariant};
|
model::{self, AddIssueModal, EditIssueModal, ModalType, Model, Page},
|
||||||
use crate::shared::{find_issue, go_to_board, ToNode};
|
shared::{
|
||||||
use crate::ws::send_ws_msg;
|
find_issue, go_to_board,
|
||||||
use crate::{model, FieldChange, FieldId, Msg, WebSocketChanged};
|
styled_confirm_modal::StyledConfirmModal,
|
||||||
|
styled_modal::{StyledModal, Variant as ModalVariant},
|
||||||
|
ToNode,
|
||||||
|
},
|
||||||
|
ws::send_ws_msg,
|
||||||
|
FieldChange, FieldId, Msg, WebSocketChanged,
|
||||||
|
};
|
||||||
|
|
||||||
mod add_issue;
|
|
||||||
mod confirm_delete_issue;
|
mod confirm_delete_issue;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod debug_modal;
|
mod debug_modal;
|
||||||
mod delete_issue_status;
|
mod delete_issue_status;
|
||||||
mod issue_details;
|
pub mod issues;
|
||||||
pub mod time_tracking;
|
pub mod time_tracking;
|
||||||
|
|
||||||
pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: &Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::collections::hash_map::HashMap;
|
use std::collections::hash_map::HashMap;
|
||||||
|
|
||||||
use chrono::{prelude::*, NaiveDate};
|
use chrono::{prelude::*, NaiveDate};
|
||||||
|
use seed::app::Orders;
|
||||||
use seed::browser::web_socket::WebSocket;
|
use seed::browser::web_socket::WebSocket;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
@ -15,7 +16,14 @@ use crate::shared::styled_image_input::StyledImageInputState;
|
|||||||
use crate::shared::styled_input::StyledInputState;
|
use crate::shared::styled_input::StyledInputState;
|
||||||
use crate::shared::styled_rte::StyledRteState;
|
use crate::shared::styled_rte::StyledRteState;
|
||||||
use crate::shared::styled_select::StyledSelectState;
|
use crate::shared::styled_select::StyledSelectState;
|
||||||
use crate::{EditIssueModalSection, FieldId, ProjectFieldId /*HOST_URL*/};
|
use crate::{EditIssueModalSection, FieldId, Msg, ProjectFieldId};
|
||||||
|
|
||||||
|
pub trait IssueModal {
|
||||||
|
fn epic_id_value(&self) -> Option<u32>;
|
||||||
|
fn epic_state(&self) -> &StyledSelectState;
|
||||||
|
|
||||||
|
fn update_states(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||||
pub enum ModalType {
|
pub enum ModalType {
|
||||||
@ -61,6 +69,7 @@ pub struct EditIssueModal {
|
|||||||
pub reporter_state: StyledSelectState,
|
pub reporter_state: StyledSelectState,
|
||||||
pub assignees_state: StyledSelectState,
|
pub assignees_state: StyledSelectState,
|
||||||
pub priority_state: StyledSelectState,
|
pub priority_state: StyledSelectState,
|
||||||
|
pub epic_state: StyledSelectState,
|
||||||
|
|
||||||
pub estimate: StyledInputState,
|
pub estimate: StyledInputState,
|
||||||
pub estimate_select: StyledSelectState,
|
pub estimate_select: StyledSelectState,
|
||||||
@ -75,6 +84,31 @@ pub struct EditIssueModal {
|
|||||||
pub comment_form: CommentForm,
|
pub comment_form: CommentForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IssueModal for EditIssueModal {
|
||||||
|
fn epic_id_value(&self) -> Option<u32> {
|
||||||
|
self.epic_state.values.get(0).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epic_state(&self) -> &StyledSelectState {
|
||||||
|
&self.epic_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_states(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) {
|
||||||
|
self.top_type_state.update(msg, orders);
|
||||||
|
self.status_state.update(msg, orders);
|
||||||
|
self.reporter_state.update(msg, orders);
|
||||||
|
self.assignees_state.update(msg, orders);
|
||||||
|
self.priority_state.update(msg, orders);
|
||||||
|
self.estimate.update(msg);
|
||||||
|
self.estimate_select.update(msg, orders);
|
||||||
|
self.time_spent.update(msg);
|
||||||
|
self.time_spent_select.update(msg, orders);
|
||||||
|
self.time_remaining.update(msg);
|
||||||
|
self.time_remaining_select.update(msg, orders);
|
||||||
|
self.epic_state.update(msg, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EditIssueModal {
|
impl EditIssueModal {
|
||||||
pub fn new(issue: &Issue, time_tracking_type: TimeTracking) -> Self {
|
pub fn new(issue: &Issue, time_tracking_type: TimeTracking) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -115,6 +149,14 @@ impl EditIssueModal {
|
|||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Priority)),
|
||||||
vec![issue.priority.into()],
|
vec![issue.priority.into()],
|
||||||
),
|
),
|
||||||
|
epic_state: StyledSelectState::new(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Epic)),
|
||||||
|
issue
|
||||||
|
.epic_id
|
||||||
|
.as_ref()
|
||||||
|
.map(|id| vec![*id as u32])
|
||||||
|
.unwrap_or_default(),
|
||||||
|
),
|
||||||
estimate: StyledInputState::new(
|
estimate: StyledInputState::new(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)),
|
FieldId::EditIssueModal(EditIssueModalSection::Issue(IssueFieldId::Estimate)),
|
||||||
value_for_time_tracking(&issue.estimate, &time_tracking_type),
|
value_for_time_tracking(&issue.estimate, &time_tracking_type),
|
||||||
@ -175,6 +217,25 @@ pub struct AddIssueModal {
|
|||||||
pub epic_state: StyledSelectState,
|
pub epic_state: StyledSelectState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IssueModal for AddIssueModal {
|
||||||
|
fn epic_id_value(&self) -> Option<u32> {
|
||||||
|
self.epic_state.values.get(0).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn epic_state(&self) -> &StyledSelectState {
|
||||||
|
&self.epic_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_states(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) {
|
||||||
|
self.title_state.update(msg);
|
||||||
|
self.assignees_state.update(msg, orders);
|
||||||
|
self.reporter_state.update(msg, orders);
|
||||||
|
self.type_state.update(msg, orders);
|
||||||
|
self.priority_state.update(msg, orders);
|
||||||
|
self.epic_state.update(msg, orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for AddIssueModal {
|
impl Default for AddIssueModal {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use seed::prelude::{Method, Orders, Request};
|
use seed::prelude::{Method, Orders, Request};
|
||||||
use web_sys::FormData;
|
use web_sys::FormData;
|
||||||
|
|
||||||
use jirs_data::{UsersFieldId, WsMsg};
|
use jirs_data::{ProjectId, UsersFieldId, WsMsg};
|
||||||
|
|
||||||
use crate::model::{Model, Page, PageContent, ProfilePage};
|
use crate::model::{Model, Page, PageContent, ProfilePage};
|
||||||
use crate::shared::styled_select::StyledSelectChange;
|
use crate::shared::styled_select::StyledSelectChange;
|
||||||
@ -69,12 +69,12 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::Profile(UsersFieldId::CurrentProject),
|
FieldId::Profile(UsersFieldId::CurrentProject),
|
||||||
StyledSelectChange::Changed(id),
|
StyledSelectChange::Changed(Some(id)),
|
||||||
) => {
|
) => {
|
||||||
if let Some(up) = model
|
if let Some(up) = model
|
||||||
.user_projects
|
.user_projects
|
||||||
.iter()
|
.iter()
|
||||||
.find(|up| up.project_id == id as i32)
|
.find(|up| up.project_id == id as ProjectId)
|
||||||
{
|
{
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
WsMsg::UserProjectSetCurrent(up.id),
|
WsMsg::UserProjectSetCurrent(up.id),
|
||||||
|
@ -68,7 +68,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::ProjectSettings(ProjectFieldId::Category),
|
FieldId::ProjectSettings(ProjectFieldId::Category),
|
||||||
StyledSelectChange::Changed(value),
|
StyledSelectChange::Changed(Some(value)),
|
||||||
) => {
|
) => {
|
||||||
let category = value.into();
|
let category = value.into();
|
||||||
page.payload.category = Some(category);
|
page.payload.category = Some(category);
|
||||||
|
@ -242,6 +242,7 @@ pub struct StyledIconBuilder {
|
|||||||
size: Option<i32>,
|
size: Option<i32>,
|
||||||
class_list: Vec<String>,
|
class_list: Vec<String>,
|
||||||
style_list: Vec<String>,
|
style_list: Vec<String>,
|
||||||
|
on_click: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledIconBuilder {
|
impl StyledIconBuilder {
|
||||||
@ -266,12 +267,18 @@ impl StyledIconBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_click(mut self, on_click: EventHandler<Msg>) -> Self {
|
||||||
|
self.on_click = Some(on_click);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> StyledIcon {
|
pub fn build(self) -> StyledIcon {
|
||||||
StyledIcon {
|
StyledIcon {
|
||||||
icon: self.icon,
|
icon: self.icon,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
class_list: self.class_list,
|
class_list: self.class_list,
|
||||||
style_list: self.style_list,
|
style_list: self.style_list,
|
||||||
|
on_click: self.on_click,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,6 +288,7 @@ pub struct StyledIcon {
|
|||||||
size: Option<i32>,
|
size: Option<i32>,
|
||||||
class_list: Vec<String>,
|
class_list: Vec<String>,
|
||||||
style_list: Vec<String>,
|
style_list: Vec<String>,
|
||||||
|
on_click: Option<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledIcon {
|
impl StyledIcon {
|
||||||
@ -290,6 +298,7 @@ impl StyledIcon {
|
|||||||
size: None,
|
size: None,
|
||||||
class_list: vec![],
|
class_list: vec![],
|
||||||
style_list: vec![],
|
style_list: vec![],
|
||||||
|
on_click: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,6 +315,7 @@ pub fn render(values: StyledIcon) -> Node<Msg> {
|
|||||||
size,
|
size,
|
||||||
mut class_list,
|
mut class_list,
|
||||||
mut style_list,
|
mut style_list,
|
||||||
|
on_click,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
if let Some(s) = icon.to_color() {
|
if let Some(s) = icon.to_color() {
|
||||||
@ -320,6 +330,7 @@ pub fn render(values: StyledIcon) -> Node<Msg> {
|
|||||||
|
|
||||||
i![
|
i![
|
||||||
attrs![At::Class => class_list.join(" "), At::Style => style_list.join(";")],
|
attrs![At::Class => class_list.join(" "), At::Style => style_list.join(";")],
|
||||||
|
on_click,
|
||||||
""
|
""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use crate::{FieldId, Msg};
|
|||||||
pub enum StyledSelectChange {
|
pub enum StyledSelectChange {
|
||||||
Text(String),
|
Text(String),
|
||||||
DropDownVisibility(bool),
|
DropDownVisibility(bool),
|
||||||
Changed(u32),
|
Changed(Option<u32>),
|
||||||
RemoveMulti(u32),
|
RemoveMulti(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +75,16 @@ impl StyledSelectState {
|
|||||||
{
|
{
|
||||||
self.text_filter = text.clone();
|
self.text_filter = text.clone();
|
||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(v))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(Some(v)))
|
||||||
if field_id == &self.field_id =>
|
if field_id == &self.field_id =>
|
||||||
{
|
{
|
||||||
self.values = vec![*v];
|
self.values = vec![*v];
|
||||||
}
|
}
|
||||||
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(None))
|
||||||
|
if field_id == &self.field_id =>
|
||||||
|
{
|
||||||
|
self.values.clear();
|
||||||
|
}
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::RemoveMulti(v))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::RemoveMulti(v))
|
||||||
if field_id == &self.field_id =>
|
if field_id == &self.field_id =>
|
||||||
{
|
{
|
||||||
@ -104,11 +109,11 @@ pub struct StyledSelect {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
valid: bool,
|
valid: bool,
|
||||||
is_multi: bool,
|
is_multi: bool,
|
||||||
allow_clear: bool,
|
|
||||||
options: Vec<StyledSelectChildBuilder>,
|
options: Vec<StyledSelectChildBuilder>,
|
||||||
selected: Vec<StyledSelectChildBuilder>,
|
selected: Vec<StyledSelectChildBuilder>,
|
||||||
text_filter: String,
|
text_filter: String,
|
||||||
opened: bool,
|
opened: bool,
|
||||||
|
clearable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToNode for StyledSelect {
|
impl ToNode for StyledSelect {
|
||||||
@ -126,11 +131,11 @@ impl StyledSelect {
|
|||||||
name: None,
|
name: None,
|
||||||
valid: None,
|
valid: None,
|
||||||
is_multi: None,
|
is_multi: None,
|
||||||
allow_clear: None,
|
|
||||||
options: None,
|
options: None,
|
||||||
selected: None,
|
selected: None,
|
||||||
text_filter: None,
|
text_filter: None,
|
||||||
opened: None,
|
opened: None,
|
||||||
|
clearable: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,11 +148,11 @@ pub struct StyledSelectBuilder {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
valid: Option<bool>,
|
valid: Option<bool>,
|
||||||
is_multi: Option<bool>,
|
is_multi: Option<bool>,
|
||||||
allow_clear: Option<bool>,
|
|
||||||
options: Option<Vec<StyledSelectChildBuilder>>,
|
options: Option<Vec<StyledSelectChildBuilder>>,
|
||||||
selected: Option<Vec<StyledSelectChildBuilder>>,
|
selected: Option<Vec<StyledSelectChildBuilder>>,
|
||||||
text_filter: Option<String>,
|
text_filter: Option<String>,
|
||||||
opened: Option<bool>,
|
opened: Option<bool>,
|
||||||
|
clearable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyledSelectBuilder {
|
impl StyledSelectBuilder {
|
||||||
@ -159,11 +164,11 @@ impl StyledSelectBuilder {
|
|||||||
name: self.name,
|
name: self.name,
|
||||||
valid: self.valid.unwrap_or(true),
|
valid: self.valid.unwrap_or(true),
|
||||||
is_multi: self.is_multi.unwrap_or_default(),
|
is_multi: self.is_multi.unwrap_or_default(),
|
||||||
allow_clear: self.allow_clear.unwrap_or_default(),
|
|
||||||
options: self.options.unwrap_or_default(),
|
options: self.options.unwrap_or_default(),
|
||||||
selected: self.selected.unwrap_or_default(),
|
selected: self.selected.unwrap_or_default(),
|
||||||
text_filter: self.text_filter.unwrap_or_default(),
|
text_filter: self.text_filter.unwrap_or_default(),
|
||||||
opened: self.opened.unwrap_or_default(),
|
opened: self.opened.unwrap_or_default(),
|
||||||
|
clearable: self.clearable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,6 +232,11 @@ impl StyledSelectBuilder {
|
|||||||
self.is_multi = Some(true);
|
self.is_multi = Some(true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clearable(mut self) -> Self {
|
||||||
|
self.clearable = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(values: StyledSelect) -> Node<Msg> {
|
pub fn render(values: StyledSelect) -> Node<Msg> {
|
||||||
@ -237,22 +247,26 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
name,
|
name,
|
||||||
valid,
|
valid,
|
||||||
is_multi,
|
is_multi,
|
||||||
allow_clear,
|
|
||||||
options,
|
options,
|
||||||
selected,
|
selected,
|
||||||
text_filter,
|
text_filter,
|
||||||
opened,
|
opened,
|
||||||
|
clearable,
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
|
let on_text = {
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let on_text = input_ev(Ev::KeyUp, move |value| {
|
input_ev(Ev::KeyUp, move |value| {
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::Text(value))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Text(value))
|
||||||
});
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_handler = {
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let visibility_handler = mouse_ev(Ev::Click, move |_| {
|
mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::DropDownVisibility(!opened))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::DropDownVisibility(!opened))
|
||||||
});
|
})
|
||||||
|
};
|
||||||
|
|
||||||
let dropdown_style = dropdown_width
|
let dropdown_style = dropdown_width
|
||||||
.map(|n| format!("width: {}px;", n))
|
.map(|n| format!("width: {}px;", n))
|
||||||
@ -263,7 +277,19 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
select_class.push("invalid".to_string());
|
select_class.push("invalid".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let chevron_down = if (selected.is_empty() || !is_multi) && variant != Variant::Empty {
|
let action_icon = if clearable && !selected.is_empty() {
|
||||||
|
let field_id = id.clone();
|
||||||
|
let on_click = mouse_ev(Ev::Click, move |ev| {
|
||||||
|
ev.stop_propagation();
|
||||||
|
ev.prevent_default();
|
||||||
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(None))
|
||||||
|
});
|
||||||
|
StyledIcon::build(Icon::Close)
|
||||||
|
.add_class("chevronIcon")
|
||||||
|
.on_click(on_click)
|
||||||
|
.build()
|
||||||
|
.into_node()
|
||||||
|
} else if (selected.is_empty() || !is_multi) && variant != Variant::Empty {
|
||||||
StyledIcon::build(Icon::ChevronDown)
|
StyledIcon::build(Icon::ChevronDown)
|
||||||
.add_class("chevronIcon")
|
.add_class("chevronIcon")
|
||||||
.build()
|
.build()
|
||||||
@ -281,12 +307,12 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
let node = child.into_node();
|
let node = child.into_node();
|
||||||
let field_id = id.clone();
|
let field_id = id.clone();
|
||||||
let on_change = mouse_ev(Ev::Click, move |_| {
|
let on_change = mouse_ev(Ev::Click, move |_| {
|
||||||
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(value))
|
Msg::StyledSelectChanged(field_id, StyledSelectChange::Changed(Some(value)))
|
||||||
});
|
});
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => "option"],
|
attrs![At::Class => "option"],
|
||||||
on_change,
|
on_change,
|
||||||
visibility_handler.clone(),
|
on_handler.clone(),
|
||||||
node
|
node
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@ -307,11 +333,6 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
empty![]
|
empty![]
|
||||||
};
|
};
|
||||||
|
|
||||||
let clear_icon = match (opened, allow_clear) {
|
|
||||||
(true, true) => StyledIcon::build(Icon::Close).build().into_node(),
|
|
||||||
_ => empty![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let option_list = match (opened, children.is_empty()) {
|
let option_list = match (opened, children.is_empty()) {
|
||||||
(false, _) => empty![],
|
(false, _) => empty![],
|
||||||
(_, true) => seed::div![attrs![At::Class => "noOptions"], "No results"],
|
(_, true) => seed::div![attrs![At::Class => "noOptions"], "No results"],
|
||||||
@ -347,15 +368,14 @@ pub fn render(values: StyledSelect) -> Node<Msg> {
|
|||||||
}),
|
}),
|
||||||
div![
|
div![
|
||||||
attrs![At::Class => format!("valueContainer {}", variant)],
|
attrs![At::Class => format!("valueContainer {}", variant)],
|
||||||
visibility_handler,
|
on_handler,
|
||||||
value,
|
value,
|
||||||
chevron_down,
|
action_icon,
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
class!["dropDown"],
|
C!["dropDown"],
|
||||||
attrs![At::Style => dropdown_style.as_str()],
|
attrs![At::Style => dropdown_style.as_str()],
|
||||||
text_input,
|
text_input,
|
||||||
clear_icon,
|
|
||||||
option_list
|
option_list
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -74,7 +74,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
Msg::StyledSelectChanged(
|
Msg::StyledSelectChanged(
|
||||||
FieldId::Users(UsersFieldId::UserRole),
|
FieldId::Users(UsersFieldId::UserRole),
|
||||||
StyledSelectChange::Changed(role),
|
StyledSelectChange::Changed(Some(role)),
|
||||||
) => {
|
) => {
|
||||||
page.user_role = role.into();
|
page.user_role = role.into();
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use jirs_data::{Token, User};
|
use jirs_data::{Token, User};
|
||||||
|
|
||||||
use crate::db::{DbExecutor, DbPool, SyncQuery};
|
use crate::{
|
||||||
use crate::errors::ServiceErrors;
|
db::{DbExecutor, DbPool, SyncQuery},
|
||||||
|
errors::ServiceErrors,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct AuthorizeUser {
|
pub struct AuthorizeUser {
|
||||||
|
@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use jirs_data::Comment;
|
use jirs_data::Comment;
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{db::DbExecutor, errors::ServiceErrors};
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadIssueComments {
|
pub struct LoadIssueComments {
|
||||||
|
@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use jirs_data::Epic;
|
use jirs_data::Epic;
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{db::DbExecutor, errors::ServiceErrors};
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadEpics {
|
pub struct LoadEpics {
|
||||||
|
@ -8,10 +8,14 @@ use jirs_data::{
|
|||||||
User, UserId, UserRole, UsernameString,
|
User, UserId, UserRole, UsernameString,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::db::tokens::CreateBindToken;
|
use crate::{
|
||||||
use crate::db::users::{LookupUser, Register};
|
db::{
|
||||||
use crate::db::DbExecutor;
|
tokens::CreateBindToken,
|
||||||
use crate::errors::ServiceErrors;
|
users::{LookupUser, Register},
|
||||||
|
DbExecutor,
|
||||||
|
},
|
||||||
|
errors::ServiceErrors,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ListInvitation {
|
pub struct ListInvitation {
|
||||||
pub user_id: UserId,
|
pub user_id: UserId,
|
||||||
|
@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use jirs_data::IssueAssignee;
|
use jirs_data::IssueAssignee;
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{db::DbExecutor, errors::ServiceErrors};
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadAssignees {
|
pub struct LoadAssignees {
|
||||||
|
@ -4,8 +4,7 @@ use diesel::prelude::*;
|
|||||||
|
|
||||||
use jirs_data::{IssueStatus, IssueStatusId, Position, ProjectId, TitleString};
|
use jirs_data::{IssueStatus, IssueStatusId, Position, ProjectId, TitleString};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{db::DbExecutor, errors::ServiceErrors};
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
|
|
||||||
pub struct LoadIssueStatuses {
|
pub struct LoadIssueStatuses {
|
||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
|
@ -6,9 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use jirs_data::{IssuePriority, IssueStatusId, IssueType};
|
use jirs_data::{IssuePriority, IssueStatusId, IssueType};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::{db::DbExecutor, errors::ServiceErrors, models::Issue};
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
use crate::models::Issue;
|
|
||||||
|
|
||||||
const FAILED_CONNECT_USER_AND_ISSUE: &str = "Failed to create connection between user and issue";
|
const FAILED_CONNECT_USER_AND_ISSUE: &str = "Failed to create connection between user and issue";
|
||||||
|
|
||||||
@ -88,6 +86,7 @@ pub struct UpdateIssue {
|
|||||||
pub user_ids: Option<Vec<i32>>,
|
pub user_ids: Option<Vec<i32>>,
|
||||||
pub reporter_id: Option<i32>,
|
pub reporter_id: Option<i32>,
|
||||||
pub issue_status_id: Option<i32>,
|
pub issue_status_id: Option<i32>,
|
||||||
|
pub epic_id: Option<Option<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for UpdateIssue {
|
impl Message for UpdateIssue {
|
||||||
@ -127,6 +126,7 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
.map(|project_id| dsl::project_id.eq(project_id)),
|
.map(|project_id| dsl::project_id.eq(project_id)),
|
||||||
msg.reporter_id
|
msg.reporter_id
|
||||||
.map(|reporter_id| dsl::reporter_id.eq(reporter_id)),
|
.map(|reporter_id| dsl::reporter_id.eq(reporter_id)),
|
||||||
|
msg.epic_id.map(|epic_id| dsl::epic_id.eq(epic_id)),
|
||||||
dsl::updated_at.eq(chrono::Utc::now().naive_utc()),
|
dsl::updated_at.eq(chrono::Utc::now().naive_utc()),
|
||||||
));
|
));
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -3,9 +3,13 @@ use diesel::prelude::*;
|
|||||||
|
|
||||||
use jirs_data::{BindToken, Message, MessageId, MessageType, User, UserId};
|
use jirs_data::{BindToken, Message, MessageId, MessageType, User, UserId};
|
||||||
|
|
||||||
use crate::db::users::{FindUser, LookupUser};
|
use crate::{
|
||||||
use crate::db::DbExecutor;
|
db::{
|
||||||
use crate::errors::ServiceErrors;
|
users::{FindUser, LookupUser},
|
||||||
|
DbExecutor,
|
||||||
|
},
|
||||||
|
errors::ServiceErrors,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LoadMessages {
|
pub struct LoadMessages {
|
||||||
|
@ -3,7 +3,7 @@ use diesel::pg::Pg;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use jirs_data::{Project, ProjectCategory, TimeTracking, UserId};
|
use jirs_data::{NameString, Project, ProjectCategory, ProjectId, TimeTracking, UserId};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
@ -11,7 +11,7 @@ use crate::schema::projects::all_columns;
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadCurrentProject {
|
pub struct LoadCurrentProject {
|
||||||
pub project_id: i32,
|
pub project_id: ProjectId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message for LoadCurrentProject {
|
impl Message for LoadCurrentProject {
|
||||||
@ -43,7 +43,7 @@ impl Handler<LoadCurrentProject> for DbExecutor {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct CreateProject {
|
pub struct CreateProject {
|
||||||
pub name: String,
|
pub name: NameString,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub category: Option<ProjectCategory>,
|
pub category: Option<ProjectCategory>,
|
||||||
@ -82,8 +82,8 @@ impl Handler<CreateProject> for DbExecutor {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct UpdateProject {
|
pub struct UpdateProject {
|
||||||
pub project_id: i32,
|
pub project_id: ProjectId,
|
||||||
pub name: Option<String>,
|
pub name: Option<NameString>,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub category: Option<ProjectCategory>,
|
pub category: Option<ProjectCategory>,
|
||||||
|
@ -60,6 +60,9 @@ impl WsHandler<UpdateIssueHandler> for WebSocketActor {
|
|||||||
(IssueFieldId::TimeRemaining, PayloadVariant::OptionI32(o)) => {
|
(IssueFieldId::TimeRemaining, PayloadVariant::OptionI32(o)) => {
|
||||||
msg.time_remaining = o;
|
msg.time_remaining = o;
|
||||||
}
|
}
|
||||||
|
(IssueFieldId::Epic, PayloadVariant::OptionI32(o)) => {
|
||||||
|
msg.epic_id = Some(o);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user