diff --git a/src/ts/components/CardWithIcon/Banner.tsx b/src/ts/components/CardWithIcon/components/Banner.tsx
similarity index 92%
rename from src/ts/components/CardWithIcon/Banner.tsx
rename to src/ts/components/CardWithIcon/components/Banner.tsx
index d125375..6646c88 100644
--- a/src/ts/components/CardWithIcon/Banner.tsx
+++ b/src/ts/components/CardWithIcon/components/Banner.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import Banner from 'ts/components/Banner';
-import style from './index.module.scss';
+import style from '../index.module.scss';
interface ICardWithBannerProps {
long?: boolean;
diff --git a/src/ts/components/CardWithIcon/components/Scoring.tsx b/src/ts/components/CardWithIcon/components/Scoring.tsx
new file mode 100644
index 0000000..bb0fecd
--- /dev/null
+++ b/src/ts/components/CardWithIcon/components/Scoring.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import style from '../index.module.scss';
+
+export interface IScoringProps {
+ title?: string;
+ value?: number;
+ total?: number;
+}
+
+function Scoring({
+ title,
+ value,
+ total,
+}: IScoringProps): React.ReactElement | null {
+ const { t } = useTranslation();
+ if (!value) return null;
+
+ return (
+
+ {`${value} / ${total || value}`}
+
+ );
+}
+
+Scoring.defaultProps = {
+ title: undefined,
+ value: undefined,
+ total: undefined,
+};
+
+export default Scoring;
diff --git a/src/ts/components/CardWithIcon/index.module.scss b/src/ts/components/CardWithIcon/index.module.scss
index 77a4b8c..a77a6ab 100644
--- a/src/ts/components/CardWithIcon/index.module.scss
+++ b/src/ts/components/CardWithIcon/index.module.scss
@@ -2,6 +2,8 @@
.card_with_icon,
.card_with_icon_long {
+ position: relative;
+
display: inline-block;
width: calc(50% - 12px);
min-height: 270px;
@@ -11,8 +13,9 @@
vertical-align: top;
text-decoration: none;
box-sizing: border-box;
+ text-align: center;
- border-radius: 8px;
+ border-radius: var(--border-radius-m);
border: 1px solid var(--color-border);
background-color: var(--color-white);
@@ -40,7 +43,8 @@
}
&_title,
- &_description {
+ &_description,
+ &_scoring {
font-weight: 100;
display: block;
margin: 0 auto;
@@ -57,7 +61,8 @@
margin: 0 0 4px 0;
}
- &_description {
+ &_description,
+ &_scoring {
font-size: var(--font-xs);
line-height: 16px;
color: var(--color-grey);
@@ -68,6 +73,22 @@
padding: 0;
line-height: 270px;
}
+
+ &_scoring {
+ position: absolute;
+ bottom: -11px;
+ left: 30%;
+ right: 30%;
+
+ display: inline-block;
+ padding: var(--space-xxxs) var(--space-xs);
+ white-space: nowrap;
+ text-align: center;
+
+ border-radius: var(--border-radius-s);
+ border: 1px solid var(--color-border);
+ background-color: var(--color-white);
+ }
}
.card_with_icon_long {
diff --git a/src/ts/components/CardWithIcon/index.tsx b/src/ts/components/CardWithIcon/index.tsx
index 877a73e..ee8a529 100644
--- a/src/ts/components/CardWithIcon/index.tsx
+++ b/src/ts/components/CardWithIcon/index.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
+import type { IScoringProps } from './components/Scoring';
+import Scoring from './components/Scoring';
import style from './index.module.scss';
interface ICardWithIconProps {
@@ -11,6 +13,7 @@ interface ICardWithIconProps {
color?: string;
icon?: string;
long?: boolean;
+ scoring?: IScoringProps;
}
function CardWithIcon({
@@ -21,8 +24,10 @@ function CardWithIcon({
color,
icon,
long = false,
+ scoring,
}: ICardWithIconProps): React.ReactElement | null {
const { t } = useTranslation();
+
if (!value && value !== 0) return null;
return (
@@ -48,6 +53,11 @@ function CardWithIcon({
{t(description || '')}
+
);
}
@@ -58,6 +68,7 @@ CardWithIcon.defaultProps = {
color: undefined,
icon: undefined,
long: false,
+ scoring: undefined,
};
export default CardWithIcon;
diff --git a/src/ts/components/CustomSelect/components/List.tsx b/src/ts/components/CustomSelect/components/List.tsx
index c0dfa56..292c0d4 100644
--- a/src/ts/components/CustomSelect/components/List.tsx
+++ b/src/ts/components/CustomSelect/components/List.tsx
@@ -1,12 +1,13 @@
import React, { useEffect, useState } from 'react';
import UiKitSelectOption from './Option';
+import IOption from '../interfaces/Option';
import style from '../styles/index.module.scss';
interface UiKitSelectListProps {
value: any;
- options: any;
+ options: IOption[];
search?: string;
keyCode?: string;
setKeyCode: Function;
@@ -26,8 +27,10 @@ function UiKitSelectList({
const [selectedIndex, setSelectedIndex] = useState(-1);
console.log(value);
- const searchResult = options
- ?.filter((option: any) => option.title.indexOf(search) !== -1);
+ const searchText = search ? search.toLowerCase() : '';
+ const searchResult = searchText
+ ? options?.filter((option: any) => option?._textForSearch?.indexOf(searchText) !== -1)
+ : options;
useEffect(() => {
if (!keyCode) return;
diff --git a/src/ts/components/CustomSelect/components/Value.tsx b/src/ts/components/CustomSelect/components/Value.tsx
index 0705535..d11d98b 100644
--- a/src/ts/components/CustomSelect/components/Value.tsx
+++ b/src/ts/components/CustomSelect/components/Value.tsx
@@ -1,10 +1,11 @@
import React from 'react';
+import IOption from '../interfaces/Option';
import style from '../styles/index.module.scss';
interface UiKitSelectValueProps {
value: any;
- options: any;
+ options: IOption[];
className?: string;
onClick: (event: React.MouseEvent) => void;
}
diff --git a/src/ts/components/CustomSelect/helpers/index.ts b/src/ts/components/CustomSelect/helpers/index.ts
index 0b2d853..8daf3a9 100644
--- a/src/ts/components/CustomSelect/helpers/index.ts
+++ b/src/ts/components/CustomSelect/helpers/index.ts
@@ -1,3 +1,5 @@
+import IOption from '../interfaces/Option';
+
function getStringFromObject(value: any) {
return value?.title
|| value?.name
@@ -23,8 +25,9 @@ function getValue(
formatter: (a: any, i?: number) => string,
) {
const type = typeof value;
- if (type === 'boolean') return value ? 'yes' : 'no';
- if (type === 'number' || type === 'string') return value;
+ if (type === 'boolean') return value ? 'true' : 'false';
+ if (type === 'number') return `${value}`;
+ if (type === 'string') return value;
if (!value) return '';
return Array.isArray(value)
@@ -40,10 +43,12 @@ export function getId(value: any, index: number) {
return getValue(value, (v: any) => getIdFromObject(v, index));
}
-export function getOption(value: any, index: number) {
+export function getOption(value: any, index: number): IOption {
+ const title = getTitle(value);
return {
id: getId(value, index),
- title: getTitle(value),
+ title,
+ _textForSearch: title.toLowerCase(),
source: value,
};
}
diff --git a/src/ts/components/CustomSelect/index.tsx b/src/ts/components/CustomSelect/index.tsx
index 2c502ab..eb5f89d 100644
--- a/src/ts/components/CustomSelect/index.tsx
+++ b/src/ts/components/CustomSelect/index.tsx
@@ -1,5 +1,6 @@
import React, { useMemo, useState } from 'react';
+import IOption from './interfaces/Option';
import UiKitSelectValue from './components/Value';
import UiKitSelectSearch from './components/Search';
import UiKitSelectList from './components/List';
@@ -25,7 +26,7 @@ function UiKitSelect({
const [search, setSearch] = useState('');
const [keyCode, setKeyCode] = useState('');
- const formattedOptions = useMemo(() => options?.map(getOption) || [], [options]);
+ const formattedOptions: IOption[] = useMemo(() => options?.map(getOption) || [], [options]);
const formattedValue = useMemo(() => {
const selectedOption = options.find((option: any) => option.id === value);
return getTitle(selectedOption) || getTitle(value);
diff --git a/src/ts/components/CustomSelect/interfaces/Option.ts b/src/ts/components/CustomSelect/interfaces/Option.ts
new file mode 100644
index 0000000..8a8fe9c
--- /dev/null
+++ b/src/ts/components/CustomSelect/interfaces/Option.ts
@@ -0,0 +1,6 @@
+export default interface IOption {
+ id: string | number | boolean;
+ title: string;
+ _textForSearch: string;
+ source: any;
+}
diff --git a/src/ts/helpers/DataGrip/components/scoring.ts b/src/ts/helpers/DataGrip/components/scoring.ts
new file mode 100644
index 0000000..24c5fff
--- /dev/null
+++ b/src/ts/helpers/DataGrip/components/scoring.ts
@@ -0,0 +1,61 @@
+import IHashMap from 'ts/interfaces/HashMap';
+
+const PROPERTIES = [
+ { property: 'daysWorked', sort: 1 },
+ { property: 'daysLosses', sort: -1 },
+ { property: 'commits', sort: 1 },
+ { property: 'daysForTask', sort: -1 },
+ { property: 'tasks', sort: 1 },
+ { property: 'moneyAll', sort: 1 },
+ { property: 'moneyWorked', sort: 1 },
+ { property: 'moneyLosses', sort: -1 },
+ { property: 'weekendPayment', sort: -1 },
+];
+
+export default class DataGripByScoring {
+ total: IHashMap = {};
+
+ statisticByName: IHashMap = {};
+
+ constructor() {
+ this.clear();
+ }
+
+ clear() {
+ this.total = {};
+ this.statisticByName = {};
+ }
+
+ updateTotalInfo(dataGripByAuthor: any) {
+ const list = [...dataGripByAuthor.statistic];
+
+ list.forEach((user: any) => {
+ this.statisticByName[user.author] = {};
+ });
+
+ PROPERTIES.forEach((config: any) => {
+ const values = list.map((user: any) => {
+ const value = user[config.property] || 0;
+ return Array.isArray(value)
+ ? value?.length
+ : value;
+ });
+
+ const uniqValues = Array.from(new Set(values));
+ const places = uniqValues
+ .sort((a:number, b:number) => (b - a) * config.sort)
+ .map((v, i) => [v, i + 1]);
+ const refValuePlace = Object.fromEntries(places);
+
+ list.forEach((user: any) => {
+ const userValue = user[config.property];
+ const userFormattedValue = Array.isArray(userValue)
+ ? userValue?.length
+ : userValue;
+ this.statisticByName[user.author][config.property] = refValuePlace[userFormattedValue];
+ });
+
+ this.total[config.property] = uniqValues.length;
+ });
+ }
+}
diff --git a/src/ts/helpers/DataGrip/index.ts b/src/ts/helpers/DataGrip/index.ts
index cd0ed05..49f7de4 100644
--- a/src/ts/helpers/DataGrip/index.ts
+++ b/src/ts/helpers/DataGrip/index.ts
@@ -13,6 +13,7 @@ import DataGripByGet from './components/get';
import DataGripByPR from './components/pr';
import DataGripByTasks from './components/tasks';
import DataGripByRelease from './components/release';
+import DataGripByScoring from './components/scoring';
class DataGrip {
firstLastCommit: any = new MinMaxCounter();
@@ -39,6 +40,8 @@ class DataGrip {
release: any = new DataGripByRelease();
+ scoring: any = new DataGripByScoring();
+
clear() {
this.firstLastCommit.clear();
this.author.clear();
@@ -52,6 +55,7 @@ class DataGrip {
this.pr.clear();
this.tasks.clear();
this.release.clear();
+ this.scoring.clear();
}
addCommit(commit: ICommit | ISystemCommit) {
@@ -81,6 +85,7 @@ class DataGrip {
this.pr.updateTotalInfo(this.author);
this.tasks.updateTotalInfo(this.pr);
this.release.updateTotalInfo();
+ this.scoring.updateTotalInfo(this.author);
}
}
diff --git a/src/ts/pages/Person/components/Money.tsx b/src/ts/pages/Person/components/Money.tsx
index 1a70a95..070b582 100644
--- a/src/ts/pages/Person/components/Money.tsx
+++ b/src/ts/pages/Person/components/Money.tsx
@@ -15,6 +15,8 @@ import IPersonCommonProps from '../interfaces/CommonProps';
const Money = observer(({ user }: IPersonCommonProps): React.ReactElement => {
const statistic = user;
+ const scoringTotal = dataGripStore.dataGrip.scoring.total;
+ const scoring = dataGripStore.dataGrip.scoring.statisticByName[user.author];
const byTimestamp = dataGripStore.dataGrip.timestamp.statisticByAuthor[statistic.author];
const taskNumber = statistic.tasks.length;
@@ -36,24 +38,40 @@ const Money = observer(({ user }: IPersonCommonProps): React.ReactElement => {
icon="./assets/cards/money_total.png"
title="page.person.money.moneyAll.title"
description="page.person.money.moneyAll.description"
+ scoring={{
+ value: scoring.moneyAll,
+ total: scoringTotal.moneyAll,
+ }}
/>
diff --git a/src/ts/pages/Person/components/Speed.tsx b/src/ts/pages/Person/components/Speed.tsx
index e88c178..b8bd1f6 100644
--- a/src/ts/pages/Person/components/Speed.tsx
+++ b/src/ts/pages/Person/components/Speed.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite';
import { getShortNumber } from 'ts/helpers/formatter';
import CardWithIcon from 'ts/components/CardWithIcon';
-import CardWithBanner from 'ts/components/CardWithIcon/Banner';
+import CardWithBanner from 'ts/components/CardWithIcon/components/Banner';
import NothingFound from 'ts/components/NothingFound';
import IsStaff from 'ts/components/NothingFound/components/IsStaff';
import PageWrapper from 'ts/components/Page/wrapper';
diff --git a/src/ts/pages/Person/components/Total.tsx b/src/ts/pages/Person/components/Total.tsx
index 300e65a..06ad586 100644
--- a/src/ts/pages/Person/components/Total.tsx
+++ b/src/ts/pages/Person/components/Total.tsx
@@ -6,7 +6,7 @@ import achievementByAuthor from 'ts/helpers/achievement/byCompetition';
import ACHIEVEMENT_TYPE from 'ts/helpers/achievement/constants/type';
import CardWithIcon from 'ts/components/CardWithIcon';
-import CardWithBanner from 'ts/components/CardWithIcon/Banner';
+import CardWithBanner from 'ts/components/CardWithIcon/components/Banner';
import Achievements from 'ts/components/Achievement';
import Description from 'ts/components/Description';
import PageWrapper from 'ts/components/Page/wrapper';
@@ -37,6 +37,8 @@ function AchievementBlock({ title, achievements }: IAchievementBlockProps) {
const Total = observer(({ user }: IPersonCommonProps): React.ReactElement => {
const { t } = useTranslation();
const statistic = user;
+ const scoringTotal = dataGripStore.dataGrip.scoring.total;
+ const scoring = dataGripStore.dataGrip.scoring.statisticByName[user.author];
const commitsWithGet = dataGripStore.dataGrip.get.getsByAuthor[user.author];
const taskNumber = statistic.tasks.length;
const achievements = achievementByAuthor.authors[statistic.author];
@@ -51,24 +53,40 @@ const Total = observer(({ user }: IPersonCommonProps): React.ReactElement => {
icon="./assets/cards/work_days.png"
title="page.person.total.daysWorked.title"
description="page.person.total.daysWorked.description"
+ scoring={{
+ value: scoring.daysWorked,
+ total: scoringTotal.daysWorked,
+ }}
/>
diff --git a/src/ts/translations/ru/pages.ts b/src/ts/translations/ru/pages.ts
index 7ee5320..2fa7b8a 100644
--- a/src/ts/translations/ru/pages.ts
+++ b/src/ts/translations/ru/pages.ts
@@ -181,6 +181,7 @@ export default `
§ page.person.total.daysWorked.description: Учтены только дни, в которые делались коммиты
§ page.person.total.tasks.title: задач
§ page.person.total.tasks.description: Если коммиты правильно подписаны
+§ page.person.scoring.toolbar: Позиция по этой метрике, относительно других сотрудников
§ page.person.character.title: Персонаж
§ page.person.achievement.title: Достижения
§ page.person.achievement.positive: Позитивные