diff --git a/frontend/public/images/anchor BLUE.svg b/frontend/public/images/anchor BLUE.svg
new file mode 100644
index 0000000..fce474a
--- /dev/null
+++ b/frontend/public/images/anchor BLUE.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/issues-list-board/issues-list-board.module.css b/frontend/src/issues-list-board/issues-list-board.module.css
index 04006f5..37718b3 100644
--- a/frontend/src/issues-list-board/issues-list-board.module.css
+++ b/frontend/src/issues-list-board/issues-list-board.module.css
@@ -2,5 +2,41 @@
display: flex;
flex-direction: column;
width: 100%;
- background-color: rgb(225, 225, 225);
+}
+
+.boardName {
+ display: flex;
+ flex-direction: row;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.anchorIcon {
+ width: 24px;
+ padding-left: 30px;
+}
+
+.boardName a {
+ display: flex;
+}
+
+.board {
+ line-height: 30px;
+
+ background-color: rgb(255, 255, 255);
+ margin: 0;
+ padding: 0;
+ color: rgba(0, 0, 0, 0.8);
+ font-size: 24px;
+ font-family: 'Inter', sans-serif;
+ font-style: normal;
+ line-height: 40px;
+ font-weight: 600;
+ font-size: 24px;
+ line-height: 30px;
+}
+
+.boardHeader {
+ color: #202020;
+ font-family: 'Source Code Pro';
}
\ No newline at end of file
diff --git a/frontend/src/issues-list-board/issues-list-board.tsx b/frontend/src/issues-list-board/issues-list-board.tsx
index 5841192..eb51b8c 100644
--- a/frontend/src/issues-list-board/issues-list-board.tsx
+++ b/frontend/src/issues-list-board/issues-list-board.tsx
@@ -21,11 +21,16 @@ export const IssuesListBoard = observer((props: Props): JSX.Element => {
title = <>{props.store.metainfo.title}>;
}
return (
- <>
-
+
);
});
\ No newline at end of file
diff --git a/frontend/src/issues-list-board/issues-list-card.module.css b/frontend/src/issues-list-board/issues-list-card.module.css
index 8a8963f..cda112d 100644
--- a/frontend/src/issues-list-board/issues-list-card.module.css
+++ b/frontend/src/issues-list-board/issues-list-card.module.css
@@ -5,7 +5,56 @@
flex-direction: column;
}
-.issueSubject {}
+.relevanceColor {
+ width: 30px;
+}
+
+.todoBlock {
+ display: flex;
+ flex-direction: row;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.todoBlock:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.importantInformation {
+ width: 100%;
+}
+
+.timeBox {
+ color: #393838;
+ font-weight: 400;
+ font-size: 11px;
+}
+
+.positionInfo {
+ list-style-image: relative;
+ bottom: 3px;
+ right: 3px;
+ text-align: right;
+ float: right;
+}
+
+.priorityBox {
+ background-color: #FFFFFF;
+ border-radius: 2px;
+ padding: 2px 2px 3px 2px;
+ font-size: 10px;
+ line-height: 12px;
+ /* border: 0.5px solid #393838; */
+ color: #ffffff;
+}
+
+.issueSubject {
+ color: #202020;
+ text-decoration: none;
+ font-weight: 400;
+ font-size: 15px;
+ line-height: 18px;
+}
.issueStatus {}
diff --git a/frontend/src/issues-list-board/issues-list-card.tsx b/frontend/src/issues-list-board/issues-list-card.tsx
index fd05210..242546d 100644
--- a/frontend/src/issues-list-board/issues-list-card.tsx
+++ b/frontend/src/issues-list-board/issues-list-card.tsx
@@ -7,11 +7,15 @@ import * as TagsNs from '../misc-components/tags';
import * as IssueHrefNs from '../misc-components/issue-href';
import * as IssueDetailsDialogNs from '../misc-components/issue-details-dialog';
import * as UnreadedFlagNs from '../misc-components/unreaded-flag';
+import { SpentHoursToFixed } from '../utils/spent-hours-to-fixed';
+import { getStyleObjectFromString } from '../utils/style';
export type Props = {
store: IIssueStore
};
+export const defaultPriorityStyleKey = 'priorityStyle';
+
export const IssuesListCard = observer((props: Props): JSX.Element => {
const unreadedStore = UnreadedFlagNs.CreateStoreFromLocalStorage(props.store);
const detailsStore = IssueDetailsDialogNs.Store.create({
@@ -19,12 +23,15 @@ export const IssuesListCard = observer((props: Props): JSX.Element => {
visible: false,
unreadedFlagStore: unreadedStore
});
+ const priorityStyle = getStyleObjectFromString(props.store[defaultPriorityStyleKey]);
+ const tagsNewLine = (props.store.styledTags && props.store.styledTags.length > 0) ?
: null;
return (
- {e.stopPropagation(); detailsStore.show();}}>
-
-
-
-
+ { e.stopPropagation(); detailsStore.show(); }}>
+
+
+
+
+
{
id={props.store.id}
/>
- | {props.store.status.name}
- | {props.store.total_spent_hours} / {props.store.total_estimated_hours}
-
-
-
+
+
{SpentHoursToFixed(props.store.total_spent_hours)} / {SpentHoursToFixed(props.store.total_estimated_hours)}
+ {tagsNewLine}
+
+
+ {props.store.status.name}
+ {props.store.priority.name}
+
);
diff --git a/frontend/src/misc-components/issue-details-dialog.module.css b/frontend/src/misc-components/issue-details-dialog.module.css
index 2f24e96..3882587 100644
--- a/frontend/src/misc-components/issue-details-dialog.module.css
+++ b/frontend/src/misc-components/issue-details-dialog.module.css
@@ -1,3 +1,7 @@
+.reset {
+ all: initial;
+}
+
.modal {
z-index: 1000;
position: fixed;
diff --git a/frontend/src/misc-components/issue-details-dialog.tsx b/frontend/src/misc-components/issue-details-dialog.tsx
index 3a95e10..3a9e841 100644
--- a/frontend/src/misc-components/issue-details-dialog.tsx
+++ b/frontend/src/misc-components/issue-details-dialog.tsx
@@ -52,29 +52,31 @@ export const IssueDetailsDialog = observer((props: Props): JSX.Element => {
props.store.hide();
};
return (
-
-
-
-
-
-
-
-
-
-
Описание:
-
- {props.store.issue.description}
-
-
-
-
-
Комментарии:
-
+
+
+
+
+
+
+
+
+
+
+
Описание:
+
+ {props.store.issue.description}
+
+
+
+
+
Комментарии:
+
+
diff --git a/frontend/src/misc-components/tags.tsx b/frontend/src/misc-components/tags.tsx
index 8852a1d..ae749ab 100644
--- a/frontend/src/misc-components/tags.tsx
+++ b/frontend/src/misc-components/tags.tsx
@@ -14,13 +14,14 @@ export const Tags = (props: Props): JSX.Element => {
if (!props.params.tags) {
return (<>>);
}
- const label = props.params.label || 'Tags';
+ let label = props.params.label || '';
+ if (label) label = `${label}: `;
const tags = props.params.tags.map((tag) => {
return ;
}) || [];
return (
-
- {label}: {tags}
-
+ <>
+ {label}{tags}
+ >
);
}
\ No newline at end of file
diff --git a/frontend/src/misc-components/time-passed.module.css b/frontend/src/misc-components/time-passed.module.css
index c34f6ba..59cb373 100644
--- a/frontend/src/misc-components/time-passed.module.css
+++ b/frontend/src/misc-components/time-passed.module.css
@@ -1,27 +1,27 @@
.timepassedDot {
- height: 10px;
- width: 10px;
- background-color: #bbb;
+ height: 15px;
+ width: 15px;
+ background-color: #808080;
border-radius: 50%;
display: inline-block;
}
.timepassedDot.hot {
- background-color: red;
+ background-color: #92201A;
}
.timepassedDot.warm {
- background-color: orange;
+ background-color: #FA5E26;
}
.timepassedDot.comfort {
- background-color: rgba(255, 255, 0, 0.4);
+ background-color: #FAD116;
}
.timepassedDot.breezy {
- background-color: rgba(0, 255, 0, 0.4);
+ background-color: #117722;
}
.timepassedDot.cold {
- background-color: rgba(0, 0, 255, 0.1);
+ background-color: #808080;
}
\ No newline at end of file
diff --git a/frontend/src/utils/spent-hours-to-fixed.ts b/frontend/src/utils/spent-hours-to-fixed.ts
new file mode 100644
index 0000000..a7e8045
--- /dev/null
+++ b/frontend/src/utils/spent-hours-to-fixed.ts
@@ -0,0 +1,11 @@
+/**
+ * Форматирование чисел для вывода трудозатрат
+ * @param a
+ * @returns
+ */
+export const SpentHoursToFixed = (a: number|string|null|undefined): string => {
+ if (a === null || typeof a === 'undefined') return '-';
+ const res = (typeof a === 'number') ? a : Number(a);
+ if (!Number.isFinite(res)) return '-';
+ return `${parseFloat(res.toFixed(1))}`;
+};
\ No newline at end of file
diff --git a/libs/event-emitter/src/issue-enhancers/priority-styles-enhancer.ts b/libs/event-emitter/src/issue-enhancers/priority-styles-enhancer.ts
new file mode 100644
index 0000000..6da3687
--- /dev/null
+++ b/libs/event-emitter/src/issue-enhancers/priority-styles-enhancer.ts
@@ -0,0 +1,102 @@
+import { Injectable } from '@nestjs/common';
+import { RedmineTypes } from '../models/redmine-types';
+import { IssueEnhancerInterface } from './issue-enhancer-interface';
+
+export type StyleRule = {
+ default?: boolean;
+ priorityName?: string;
+ style?: string;
+ className?: string;
+};
+
+export const defaultRule: StyleRule = {
+ default: true,
+ style: 'background-color: gray; color: white;',
+};
+
+export const defaultKey = 'priorityStyle';
+
+export function ValidateStyleRule(rule: StyleRule): boolean {
+ return (
+ // check default prop
+ (typeof rule.default === 'boolean' ||
+ typeof rule.default === 'undefined') &&
+ // check priority name prop
+ (rule.default ||
+ (!rule.default &&
+ typeof rule.priorityName === 'string' &&
+ rule.priorityName.length > 0)) &&
+ // check style or className props
+ ((typeof rule.style === 'string' && rule.style.length > 0) ||
+ (typeof rule.className === 'string' && rule.className.length > 0))
+ );
+}
+
+export function HasDefaultRule(rules: StyleRule[]): boolean {
+ const hasDefaultRule = rules.find((rule) => rule.default);
+ return Boolean(hasDefaultRule);
+}
+
+@Injectable()
+export class PriorityStylesEnhancer implements IssueEnhancerInterface {
+ name: string;
+
+ private rules: StyleRule[];
+
+ private targetKey: string;
+
+ constructor(rules?: StyleRule[], targetKey?: string) {
+ let validatedRules: StyleRule[] = [];
+ if (rules && rules.length > 0) {
+ validatedRules = rules.filter(ValidateStyleRule);
+ }
+ if (!HasDefaultRule(validatedRules)) {
+ validatedRules.push(defaultRule);
+ }
+ this.rules = validatedRules;
+
+ this.targetKey =
+ typeof targetKey === 'string' && targetKey.length > 0
+ ? targetKey
+ : defaultKey;
+ }
+
+ async enhance(
+ issue: RedmineTypes.ExtendedIssue,
+ ): Promise {
+ const priorityName = issue.priority.name;
+ const priorityRule = this.findPriorityRule(priorityName);
+ issue[this.targetKey] = priorityRule.style || priorityRule.className;
+ return issue;
+ }
+
+ private findPriorityRule(priority: string): StyleRule {
+ return (this.rules.find((r) => r.priorityName === priority) ||
+ this.rules.find((r) => r.default)) as StyleRule;
+ }
+}
+
+export type PrioritiesStyleParams = {
+ rules: StyleRule[];
+ targetKey: string;
+};
+
+export type ConfigWithPriorityStyles = {
+ priorities?: PrioritiesStyleParams;
+ [key: string]: any;
+};
+
+export function CreatePriorityStylesEnhancer(
+ cfg: ConfigWithPriorityStyles,
+): PriorityStylesEnhancer {
+ let params: PrioritiesStyleParams;
+ if (cfg.priorities && cfg.priorities.rules && cfg.priorities.targetKey) {
+ params = cfg.priorities;
+ } else {
+ params = {
+ rules: [defaultRule],
+ targetKey: defaultKey,
+ };
+ }
+ return new PriorityStylesEnhancer(params.rules, params.targetKey);
+}
diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts
index 59d3792..3716036 100644
--- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts
+++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-fields.widget.service.ts
@@ -13,6 +13,7 @@ import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store';
import { Injectable, Logger } from '@nestjs/common';
import nano from 'nano';
import { WidgetInterface } from '../widget-interface';
+import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer';
export namespace ListIssuesByFieldsWidgetNs {
export type Params = {
@@ -66,6 +67,7 @@ export class ListIssuesByFieldsWidgetService
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
+ PriorityStylesEnhancerNs.CreatePriorityStylesEnhancer(widgetParams),
]);
const grouped = store.groupByStatusWithExtra((issue) => {
return this.getGroupFromIssue(issue, widgetParams);
diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts
index 173f447..d31eb6e 100644
--- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts
+++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users-like-jira.widget.service.ts
@@ -12,6 +12,7 @@ import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store';
import { Injectable, Logger } from '@nestjs/common';
import nano from 'nano';
import { WidgetInterface } from '../widget-interface';
+import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer';
export namespace ListIssuesByUsersLikeJiraWidgetNs {
export namespace Models {
@@ -61,6 +62,7 @@ export class ListIssuesByUsersLikeJiraWidgetService
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
+ PriorityStylesEnhancerNs.CreatePriorityStylesEnhancer(widgetParams),
]);
const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => {
const users = [] as string[];
diff --git a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts
index 9d3fefe..4ba3cbd 100644
--- a/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts
+++ b/libs/event-emitter/src/project-dashboard/widgets/list-issues-by-users.widget.service.ts
@@ -13,6 +13,7 @@ import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store';
import { Injectable, Logger } from '@nestjs/common';
import nano from 'nano';
import { WidgetInterface } from '../widget-interface';
+import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer';
export namespace ListIssuesByUsersWidgetNs {
export namespace Models {
@@ -73,6 +74,7 @@ export class ListIssuesByUsersWidgetService
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
+ PriorityStylesEnhancerNs.CreatePriorityStylesEnhancer(widgetParams),
]);
const grouped = store.groupByStatusWithExtra((issue) => {
const res = this.getUserValueByKey(issue, widgetParams.userKey);
diff --git a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts b/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts
index a28f7ac..94b7974 100644
--- a/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts
+++ b/libs/event-emitter/src/project-dashboard/widgets/root-issue-subtrees.widget.service.ts
@@ -12,6 +12,7 @@ import {
} from '@app/event-emitter/utils/tree-issues-store';
import { Injectable } from '@nestjs/common';
import { WidgetInterface } from '../widget-interface';
+import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer';
export namespace RootIssueSubTreesWidgetNs {
export namespace Models {
@@ -66,6 +67,7 @@ export class RootIssueSubTreesWidgetService
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
+ PriorityStylesEnhancerNs.CreatePriorityStylesEnhancer(widgetParams),
]);
let stories: TreeIssuesStoreNs.Models.GetFlatStories.Result;
if (widgetParams.parentsAsGroups) {
diff --git a/src/dashboards/widgets/issues-by-tags.widget.service.ts b/src/dashboards/widgets/issues-by-tags.widget.service.ts
index c199eb1..13e2e9c 100644
--- a/src/dashboards/widgets/issues-by-tags.widget.service.ts
+++ b/src/dashboards/widgets/issues-by-tags.widget.service.ts
@@ -12,6 +12,7 @@ import { GetValueFromObjectByKey } from '@app/event-emitter/utils/get-value-from
import { TreeIssuesStore } from '@app/event-emitter/utils/tree-issues-store';
import { Injectable, Logger } from '@nestjs/common';
import nano from 'nano';
+import * as PriorityStylesEnhancerNs from '@app/event-emitter/issue-enhancers/priority-styles-enhancer';
export namespace IssuesByTagsWidgetNs {
export type Params = {
@@ -59,6 +60,7 @@ export class IssuesByTagsWidgetService
this.timePassedHighlightEnhancer,
this.issueUrlEnhancer,
TagStyledEnhancerNs.CreateTagStyledEnhancerForConfig(widgetParams),
+ PriorityStylesEnhancerNs.CreatePriorityStylesEnhancer(widgetParams),
]);
const grouped = store.groupByStatusWithExtraToMultipleStories((issue) => {
if (!issue || !widgetParams.tags || !widgetParams.tags.tagsKeyName) {