Add issues and filters
This commit is contained in:
parent
3ee08dea54
commit
e6778db137
178
jirs-client/js/css/issuesAndFilters.scss
Normal file
178
jirs-client/js/css/issuesAndFilters.scss
Normal file
@ -0,0 +1,178 @@
|
||||
#issuesAndFilters {
|
||||
display: block;
|
||||
|
||||
> .filters {
|
||||
margin: {
|
||||
top: 5px;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
> .pseudoInput {
|
||||
display: flex;
|
||||
|
||||
> .styledIcon {
|
||||
line-height: 39px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
> .styledSelect {
|
||||
display: inline;
|
||||
min-width: 20px;
|
||||
|
||||
> .valueContainer {
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .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;
|
||||
|
||||
> span {
|
||||
margin: 0 5px;
|
||||
font-family: var(--font-medium);
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
line-height: 2;
|
||||
appearance: none;
|
||||
cursor: none;
|
||||
user-select: none;
|
||||
font-size: 14.5px;
|
||||
}
|
||||
|
||||
> .styledLink, > span {
|
||||
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;
|
||||
|
||||
> .issueLink {
|
||||
display: inline;
|
||||
cursor: alias;
|
||||
}
|
||||
}
|
||||
|
||||
> .priority {
|
||||
grid-area: priority;
|
||||
}
|
||||
|
||||
> .name {
|
||||
grid-area: name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,18 +9,18 @@
|
||||
@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";
|
||||
@ -35,3 +35,4 @@
|
||||
@import "css/invite.scss";
|
||||
@import "css/reports.scss";
|
||||
@import "css/profile.scss";
|
||||
@import "css/issuesAndFilters.scss";
|
||||
|
@ -23,7 +23,7 @@ impl InputVariant {
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
pub struct StyledInputState {
|
||||
id: FieldId,
|
||||
pub field_id: FieldId,
|
||||
touched: bool,
|
||||
pub value: String,
|
||||
pub min: Option<usize>,
|
||||
@ -37,7 +37,7 @@ impl StyledInputState {
|
||||
S: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id,
|
||||
field_id: id,
|
||||
touched: false,
|
||||
value: value.into(),
|
||||
min: None,
|
||||
@ -75,7 +75,7 @@ impl StyledInputState {
|
||||
#[inline(always)]
|
||||
pub fn update(&mut self, msg: &Msg) {
|
||||
match msg {
|
||||
Msg::StrInputChanged(field_id, s) if field_id == &self.id => {
|
||||
Msg::StrInputChanged(field_id, s) if field_id == &self.field_id => {
|
||||
self.value = s.clone();
|
||||
self.touched = true;
|
||||
}
|
||||
|
@ -5,23 +5,19 @@ use seed::*;
|
||||
|
||||
use crate::Msg;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StyledLink<'l> {
|
||||
pub children: Vec<Node<Msg>>,
|
||||
pub class_list: &'l str,
|
||||
pub href: &'l str,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl<'l> StyledLink<'l> {
|
||||
#[inline(always)]
|
||||
pub fn render(self) -> Node<Msg> {
|
||||
let StyledLink {
|
||||
children,
|
||||
class_list,
|
||||
href,
|
||||
} = self;
|
||||
|
||||
let on_click = {
|
||||
let href = href.to_string();
|
||||
let href = self.href.to_string();
|
||||
mouse_ev("click", move |ev| {
|
||||
if href.starts_with('/') {
|
||||
ev.prevent_default();
|
||||
@ -36,10 +32,10 @@ impl<'l> StyledLink<'l> {
|
||||
};
|
||||
|
||||
a![
|
||||
C!["styledLink", class_list],
|
||||
attrs![ At::Href => href ],
|
||||
C!["styledLink", self.class_list],
|
||||
attrs![ At::Href => self.href ],
|
||||
on_click,
|
||||
children,
|
||||
self.children,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,10 @@ pub struct StyledSelectState {
|
||||
}
|
||||
|
||||
impl StyledSelectState {
|
||||
pub fn field_id(&self) -> FieldId {
|
||||
self.field_id.clone()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.text_filter.clear();
|
||||
self.opened = false;
|
||||
|
@ -84,6 +84,11 @@ impl ButtonId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
pub enum IssuesAndFiltersId {
|
||||
Jql,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)]
|
||||
pub enum FieldId {
|
||||
NoField,
|
||||
@ -95,6 +100,7 @@ pub enum FieldId {
|
||||
// issue
|
||||
AddIssueModal(IssueFieldId),
|
||||
EditIssueModal(EditIssueModalSection),
|
||||
IssuesAndFilters(IssuesAndFiltersId),
|
||||
// epic
|
||||
EditEpic(EpicFieldId),
|
||||
// project boards
|
||||
@ -196,6 +202,9 @@ impl FieldId {
|
||||
EpicFieldId::TransformInto => "epicEpic-transformInto",
|
||||
},
|
||||
FieldId::Rte(..) => "rte",
|
||||
FieldId::IssuesAndFilters(sub) => match sub {
|
||||
IssuesAndFiltersId::Jql => "issuesAndFilters-jql",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ pub enum Msg {
|
||||
// issues
|
||||
AddIssue,
|
||||
DeleteIssue(EpicId),
|
||||
SetActiveIssue(Option<IssueId>),
|
||||
|
||||
// epics
|
||||
AddEpic,
|
||||
@ -256,6 +257,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::debug!("{:?}", model);
|
||||
@ -278,6 +280,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),
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,6 +312,7 @@ fn resolve_page(url: Url) -> Option<Page> {
|
||||
Some(Ok(id)) => Page::EditEpic(id),
|
||||
_ => return None,
|
||||
},
|
||||
"issues-and-filters" => Page::IssuesAndFilters,
|
||||
_ => Page::Project,
|
||||
};
|
||||
Some(page)
|
||||
|
@ -8,6 +8,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::components::styled_select::StyledSelectState;
|
||||
use crate::pages::invite_page::InvitePage;
|
||||
use crate::pages::issues_and_filters::IssuesAndFiltersPage;
|
||||
use crate::pages::profile_page::model::ProfilePage;
|
||||
use crate::pages::project_page::model::ProjectPage;
|
||||
use crate::pages::project_settings_page::ProjectSettingsPage;
|
||||
@ -75,6 +76,7 @@ pub enum Page {
|
||||
// issue
|
||||
EditIssue(EpicId),
|
||||
AddIssue,
|
||||
IssuesAndFilters,
|
||||
// settings
|
||||
ProjectSettings,
|
||||
// auth
|
||||
@ -95,6 +97,7 @@ impl Page {
|
||||
Page::EditEpic(id) => format!("/edit-epic/{id}", id = id),
|
||||
Page::EditIssue(id) => format!("/issues/{id}", id = id),
|
||||
Page::AddIssue => "/add-issue".to_string(),
|
||||
Page::IssuesAndFilters => "/issues-and-filters".to_string(),
|
||||
Page::ProjectSettings => "/project-settings".to_string(),
|
||||
Page::SignIn => "/login".to_string(),
|
||||
Page::SignUp => "/register".to_string(),
|
||||
@ -190,6 +193,7 @@ pub enum PageContent {
|
||||
Users(Box<UsersPage>),
|
||||
Profile(Box<ProfilePage>),
|
||||
Reports(Box<ReportsPage>),
|
||||
IssuesAndFilters(Box<IssuesAndFiltersPage>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
7
jirs-client/src/pages/issues_and_filters/mod.rs
Normal file
7
jirs-client/src/pages/issues_and_filters/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub use model::*;
|
||||
pub use update::*;
|
||||
pub use view::*;
|
||||
|
||||
mod model;
|
||||
mod update;
|
||||
mod view;
|
75
jirs-client/src/pages/issues_and_filters/model.rs
Normal file
75
jirs-client/src/pages/issues_and_filters/model.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use jirs_data::{Issue, IssueId};
|
||||
use seed::app::Orders;
|
||||
|
||||
use crate::components::styled_select::StyledSelectState;
|
||||
use crate::{model, FieldId, IssuesAndFiltersId, Msg};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IssuesAndFiltersPage {
|
||||
pub visible_issues: Vec<IssueId>,
|
||||
pub active_id: Option<IssueId>,
|
||||
pub current_jql_part: StyledSelectState,
|
||||
pub jql_parts: Vec<String>,
|
||||
}
|
||||
|
||||
impl IssuesAndFiltersPage {
|
||||
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,
|
||||
current_jql_part: StyledSelectState::new(
|
||||
FieldId::IssuesAndFilters(IssuesAndFiltersId::Jql),
|
||||
vec![],
|
||||
),
|
||||
jql_parts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visible_issues(issues: &[Issue]) -> Vec<IssueId> {
|
||||
issues.iter().map(|issue| issue.id).collect()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: &Msg, orders: &mut impl Orders<Msg>) {
|
||||
self.current_jql_part.update(msg, orders);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FieldOption {
|
||||
None,
|
||||
Assignee,
|
||||
}
|
||||
|
||||
impl FieldOption {
|
||||
pub fn to_label(&self) -> &'static str {
|
||||
match self {
|
||||
FieldOption::None => "none",
|
||||
FieldOption::Assignee => "Assignee",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
FieldOption::None => " ",
|
||||
FieldOption::Assignee => "assignee",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> u32 {
|
||||
match self {
|
||||
FieldOption::None => 0,
|
||||
FieldOption::Assignee => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for FieldOption {
|
||||
fn from(n: u32) -> Self {
|
||||
match n {
|
||||
1 => FieldOption::Assignee,
|
||||
_ => FieldOption::None,
|
||||
}
|
||||
}
|
||||
}
|
73
jirs-client/src/pages/issues_and_filters/update.rs
Normal file
73
jirs-client/src/pages/issues_and_filters/update.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use seed::prelude::*;
|
||||
|
||||
use crate::components::styled_select::StyledSelectChanged;
|
||||
use crate::model::{Model, PageContent};
|
||||
use crate::pages::issues_and_filters::FieldOption;
|
||||
use crate::ws::board_load;
|
||||
use crate::{FieldId, IssuesAndFiltersId, Msg, OperationKind, Page, ResourceKind};
|
||||
|
||||
mod jql;
|
||||
|
||||
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
||||
if model.user.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(model.page, Page::IssuesAndFilters)
|
||||
&& !matches!(model.page_content, PageContent::IssuesAndFilters(..))
|
||||
{
|
||||
build_page_content(model);
|
||||
}
|
||||
|
||||
match msg {
|
||||
Msg::ResourceChanged(ResourceKind::Auth, OperationKind::SingleLoaded, Some(_))
|
||||
| Msg::ChangePage(Page::IssuesAndFilters) => {
|
||||
board_load(model, orders);
|
||||
build_page_content(model);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
crate::match_page_mut!(model, IssuesAndFilters).update(&msg, orders);
|
||||
|
||||
match msg {
|
||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::ListLoaded, _) => {
|
||||
let issues = super::IssuesAndFiltersPage::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)) => {
|
||||
crate::match_page_mut!(model, IssuesAndFilters).active_id = Some(id);
|
||||
}
|
||||
Msg::SetActiveIssue(None) => {
|
||||
let first_id = model.issues().first().as_ref().map(|issue| issue.id);
|
||||
crate::match_page_mut!(model, IssuesAndFilters).active_id = first_id;
|
||||
}
|
||||
Msg::StyledSelectChanged(
|
||||
FieldId::IssuesAndFilters(IssuesAndFiltersId::Jql),
|
||||
StyledSelectChanged::Changed(Some(n)),
|
||||
) if n != 0 => {
|
||||
let page = crate::match_page_mut!(model, IssuesAndFilters);
|
||||
match page.jql_parts.len() % 3 {
|
||||
0 => {
|
||||
let field = FieldOption::from(n);
|
||||
page.jql_parts.push(field.to_label().to_string());
|
||||
}
|
||||
1 => {}
|
||||
2 => {}
|
||||
_ => {}
|
||||
}
|
||||
page.current_jql_part.reset();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_page_content(model: &mut Model) {
|
||||
model.page_content =
|
||||
PageContent::IssuesAndFilters(Box::new(super::IssuesAndFiltersPage::new(model)));
|
||||
}
|
31
jirs-client/src/pages/issues_and_filters/update/jql.rs
Normal file
31
jirs-client/src/pages/issues_and_filters/update/jql.rs
Normal file
@ -0,0 +1,31 @@
|
||||
pub enum OpType {
|
||||
Eq,
|
||||
}
|
||||
|
||||
impl OpType {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
OpType::Eq => "=",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FieldType {
|
||||
Assignee,
|
||||
}
|
||||
|
||||
impl FieldType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
FieldType::Assignee => "Assignee",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JqlNode {
|
||||
op: OpType,
|
||||
}
|
||||
|
||||
pub fn parse(_query: &[&str]) -> JqlNode {
|
||||
JqlNode { op: OpType::Eq }
|
||||
}
|
33
jirs-client/src/pages/issues_and_filters/view.rs
Normal file
33
jirs-client/src/pages/issues_and_filters/view.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::model::Model;
|
||||
use crate::shared::inner_layout;
|
||||
use crate::Msg;
|
||||
|
||||
mod filters;
|
||||
mod issue_info;
|
||||
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
let page = crate::match_page!(model, IssuesAndFilters; 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],
|
||||
)
|
||||
}
|
53
jirs-client/src/pages/issues_and_filters/view/filters.rs
Normal file
53
jirs-client/src/pages/issues_and_filters/view/filters.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::components::styled_icon::{Icon, StyledIcon};
|
||||
use crate::components::styled_select::{SelectVariant, StyledSelect};
|
||||
use crate::components::styled_select_child::StyledSelectOption;
|
||||
use crate::model::Model;
|
||||
use crate::pages::issues_and_filters::{FieldOption, IssuesAndFiltersPage};
|
||||
use crate::Msg;
|
||||
|
||||
pub fn filters(_model: &Model, page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
let search_input = pseudo_input(page);
|
||||
div![C!["filters"], search_input]
|
||||
}
|
||||
|
||||
fn pseudo_input(page: &IssuesAndFiltersPage) -> Node<Msg> {
|
||||
let input = StyledSelect {
|
||||
id: page.current_jql_part.field_id(),
|
||||
text_filter: page.current_jql_part.text_filter.as_str(),
|
||||
variant: SelectVariant::Empty,
|
||||
dropdown_width: None,
|
||||
name: "jqlPart",
|
||||
valid: true,
|
||||
clearable: true,
|
||||
selected: page
|
||||
.current_jql_part
|
||||
.values
|
||||
.iter()
|
||||
.map(|n| field_select_option(*n))
|
||||
.collect(),
|
||||
options: Some(vec![field_select_option(1)].into_iter()),
|
||||
is_multi: false,
|
||||
opened: page.current_jql_part.opened,
|
||||
}
|
||||
.render();
|
||||
div![
|
||||
C!["pseudoInput"],
|
||||
StyledIcon::from(Icon::Search).render(),
|
||||
input
|
||||
]
|
||||
}
|
||||
|
||||
fn field_select_option<'l>(n: u32) -> StyledSelectOption<'l> {
|
||||
let v = FieldOption::from(n);
|
||||
StyledSelectOption {
|
||||
name: Some(v.to_name()),
|
||||
icon: None,
|
||||
text: Some(v.to_label()),
|
||||
value: v.to_value(),
|
||||
class_list: "",
|
||||
variant: SelectVariant::Empty,
|
||||
}
|
||||
}
|
169
jirs-client/src/pages/issues_and_filters/view/issue_info.rs
Normal file
169
jirs-client/src/pages/issues_and_filters/view/issue_info.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use jirs_data::{Issue, IssueId};
|
||||
use seed::prelude::*;
|
||||
use seed::*;
|
||||
|
||||
use crate::components::styled_icon::*;
|
||||
use crate::components::styled_link::*;
|
||||
use crate::model::Model;
|
||||
use crate::Msg;
|
||||
|
||||
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 {
|
||||
href: format!("/issues/{}", id).as_str(),
|
||||
children: vec![div![
|
||||
C!["text"],
|
||||
format!("{}-{}", project_name, id).as_str()
|
||||
]],
|
||||
disabled: true,
|
||||
..Default::default()
|
||||
}
|
||||
.render();
|
||||
|
||||
let project_link = StyledLink {
|
||||
disabled: true,
|
||||
href: "/board",
|
||||
children: vec![span![model
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|p| p.name.as_str())
|
||||
.unwrap_or_default()]],
|
||||
..Default::default()
|
||||
}
|
||||
.render();
|
||||
|
||||
let details = {
|
||||
let issue_type = {
|
||||
let type_name = issue_type.to_string();
|
||||
let type_icon = StyledIcon::from(Icon::from(*issue_type)).render();
|
||||
li![
|
||||
C!["line"],
|
||||
div![C!["detailsTitle"], "Type:"],
|
||||
div![C!["type detailsValue"], type_icon, type_name],
|
||||
]
|
||||
};
|
||||
let priority = {
|
||||
let name = priority.to_string();
|
||||
let icon = StyledIcon::from(Icon::from(*priority)).render();
|
||||
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![];
|
||||
|
||||
let project_icon = crate::images::project_avatar::render();
|
||||
|
||||
div![
|
||||
C!["issueInfo"],
|
||||
div![
|
||||
C!["header"],
|
||||
div![C!["logo"], project_icon],
|
||||
div![C!["title"], title.as_str()],
|
||||
div![C!["path"], project_link, span!["/"], 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::IssuesAndFiltersPage,
|
||||
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::IssuesAndFiltersPage,
|
||||
issue: &Issue,
|
||||
project_name: &str,
|
||||
) -> Node<Msg> {
|
||||
let link = issue_link(issue.id, project_name);
|
||||
let ty = { StyledIcon::from(Icon::from(issue.issue_type)).render() };
|
||||
let priority = { StyledIcon::from(Icon::from(issue.priority)).render() };
|
||||
|
||||
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"],
|
||||
a![
|
||||
on_click,
|
||||
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 {
|
||||
href: format!("/issues/{}", id).as_str(),
|
||||
children: vec![
|
||||
StyledIcon::from(Icon::Link).render(),
|
||||
span![format!("{}-{}", project_name, id)],
|
||||
],
|
||||
disabled: true,
|
||||
class_list: "withIcon issueLink",
|
||||
..Default::default()
|
||||
}
|
||||
.render()
|
||||
}
|
@ -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;
|
||||
|
@ -217,6 +217,7 @@ fn issue_list(page: &ReportsPage, project_name: &str, this_month_updated: &[&Iss
|
||||
],
|
||||
class_list: "withIcon",
|
||||
href: format!("/issues/{}", id).as_str(),
|
||||
..Default::default()
|
||||
}.render();
|
||||
|
||||
li![
|
||||
|
@ -63,6 +63,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
children: vec![span!["Register"]],
|
||||
class_list: "signUpLink",
|
||||
href: "/register",
|
||||
..Default::default()
|
||||
}
|
||||
.render();
|
||||
let submit_field = StyledField {
|
||||
|
@ -63,6 +63,7 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
children: vec![span!["Sign In"]],
|
||||
class_list: "signInLink",
|
||||
href: "/login",
|
||||
..Default::default()
|
||||
}
|
||||
.render();
|
||||
|
||||
|
@ -55,7 +55,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),
|
||||
|
Loading…
Reference in New Issue
Block a user