update
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "./static/css/main.44209276.css",
|
"main.css": "./static/css/main.8e65d99e.css",
|
||||||
"main.js": "./static/js/main.5a210281.js",
|
"main.js": "./static/js/main.cb604897.js",
|
||||||
"static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png",
|
"static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png",
|
||||||
"index.html": "./index.html",
|
"index.html": "./index.html",
|
||||||
"static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg",
|
"static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg",
|
||||||
"static/media/info.svg": "./static/media/info.954631f6b19e3fe9c495.svg",
|
"static/media/info.svg": "./static/media/info.954631f6b19e3fe9c495.svg",
|
||||||
"static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg",
|
"static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg",
|
||||||
"main.44209276.css.map": "./static/css/main.44209276.css.map",
|
"main.8e65d99e.css.map": "./static/css/main.8e65d99e.css.map",
|
||||||
"main.5a210281.js.map": "./static/js/main.5a210281.js.map"
|
"main.cb604897.js.map": "./static/js/main.cb604897.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.44209276.css",
|
"static/css/main.8e65d99e.css",
|
||||||
"static/js/main.5a210281.js"
|
"static/js/main.cb604897.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
41213
build/assets/log.txt
1
build/assets/menu/logout.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80H200Zm440-160-55-58 102-102H360v-80h327L585-622l55-58 200 200-200 200Z"/></svg>
|
After Width: | Height: | Size: 273 B |
1
build/assets/menu/share.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M240-40q-33 0-56.5-23.5T160-120v-440q0-33 23.5-56.5T240-640h120v80H240v440h480v-440H600v-80h120q33 0 56.5 23.5T800-560v440q0 33-23.5 56.5T720-40H240Zm200-280v-447l-64 64-56-57 160-160 160 160-56 57-64-64v447h-80Z"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -1 +1 @@
|
||||||
<!doctype html><html><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><script type="text/javascript">var report=[]</script><script src="/log.txt"></script><script src="./log.txt"></script><script src="../log.txt"></script><script src="./log-0.txt"></script><script src="./log-1.txt"></script><script src="./log-2.txt"></script><script src="./log-3.txt"></script><script src="./log-4.txt"></script><script src="./log-5.txt"></script><script src="./log-6.txt"></script><script src="./report/log-0.txt"></script><script src="./report/log-1.txt"></script><script src="./report/log-2.txt"></script><script src="./report/log-3.txt"></script><script src="./report/log-4.txt"></script><script src="./report/log-5.txt"></script><script src="./report/log-6.txt"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Git Statistics</title><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.5a210281.js"></script><link href="./static/css/main.44209276.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
<!doctype html><html><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="theme-color" content="white"/><script type="text/javascript">var report=[]</script><script src="/log.txt"></script><script src="./log.txt"></script><script src="../log.txt"></script><script src="./log-0.txt"></script><script src="./log-1.txt"></script><script src="./log-2.txt"></script><script src="./log-3.txt"></script><script src="./log-4.txt"></script><script src="./log-5.txt"></script><script src="./log-6.txt"></script><script src="./report/log-0.txt"></script><script src="./report/log-1.txt"></script><script src="./report/log-2.txt"></script><script src="./report/log-3.txt"></script><script src="./report/log-4.txt"></script><script src="./report/log-5.txt"></script><script src="./report/log-6.txt"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Git Statistics</title><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.cb604897.js"></script><link href="./static/css/main.8e65d99e.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 309 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 309 KiB |
2
build/static/css/main.8e65d99e.css
Normal file
1
build/static/css/main.8e65d99e.css.map
Normal file
3
build/static/js/main.cb604897.js
Normal file
|
@ -1,5 +1,3 @@
|
||||||
/*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
|
|
||||||
|
|
||||||
/*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
|
/*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
|
||||||
|
|
||||||
/**
|
/**
|
1
build/static/js/main.cb604897.js.map
Normal file
1
public/assets/menu/logout.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80H200Zm440-160-55-58 102-102H360v-80h327L585-622l55-58 200 200-200 200Z"/></svg>
|
After Width: | Height: | Size: 273 B |
1
public/assets/menu/share.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M240-40q-33 0-56.5-23.5T160-120v-440q0-33 23.5-56.5T240-640h120v80H240v440h480v-440H600v-80h120q33 0 56.5 23.5T800-560v440q0 33-23.5 56.5T720-40H240Zm200-280v-447l-64 64-56-57 160-160 160 160-56 57-64-64v447h-80Z"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -9,7 +9,8 @@
|
||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
<meta name="format-detection" content="address=no">
|
<meta name="format-detection" content="address=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
<meta name="theme-color" content="white"/>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var report = [];
|
var report = [];
|
||||||
|
@ -80,6 +81,6 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<noscript><div><img src="https://mc.yandex.ru/watch/94903985" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
<noscript><div><img src="https://mc.yandex.ru/watch/94903985" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
||||||
/Yandex.Metrika counter -->
|
/Yandex.Metrika counter -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 309 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 309 KiB |
1
src/assets/menu/logout.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h280v80H200Zm440-160-55-58 102-102H360v-80h327L585-622l55-58 200 200-200 200Z"/></svg>
|
After Width: | Height: | Size: 273 B |
1
src/assets/menu/share.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#84858D"><path d="M240-40q-33 0-56.5-23.5T160-120v-440q0-33 23.5-56.5T240-640h120v80H240v440h480v-440H600v-80h120q33 0 56.5 23.5T800-560v440q0 33-23.5 56.5T720-40H240Zm200-280v-447l-64 64-56-57 160-160 160 160-56 57-64-64v447h-80Z"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -3,9 +3,16 @@ import { HashRouter } from 'react-router-dom';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
import localization from 'ts/helpers/Localization';
|
import localization from 'ts/helpers/Localization';
|
||||||
import ru from 'ts/translations/ru/index';
|
import de from 'ts/translations/de';
|
||||||
import en from 'ts/translations/en/index';
|
import en from 'ts/translations/en';
|
||||||
import './ts/helpers/i18n';
|
import es from 'ts/translations/es';
|
||||||
|
import fr from 'ts/translations/fr';
|
||||||
|
import ja from 'ts/translations/ja';
|
||||||
|
import pt from 'ts/translations/pt';
|
||||||
|
import ru from 'ts/translations/ru';
|
||||||
|
import zh from 'ts/translations/zh';
|
||||||
|
|
||||||
|
import initializationI18n from './ts/helpers/i18n';
|
||||||
|
|
||||||
import Authorization from 'ts/pages/Authorization';
|
import Authorization from 'ts/pages/Authorization';
|
||||||
import userSettings from 'ts/store/UserSettings';
|
import userSettings from 'ts/store/UserSettings';
|
||||||
|
@ -23,8 +30,14 @@ if (module.hot) {
|
||||||
module.hot.accept();
|
module.hot.accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localization.parse('de', de);
|
||||||
localization.parse('en', en);
|
localization.parse('en', en);
|
||||||
|
localization.parse('es', es);
|
||||||
|
localization.parse('fr', fr);
|
||||||
|
localization.parse('ja', ja);
|
||||||
|
localization.parse('pt', pt);
|
||||||
localization.parse('ru', ru);
|
localization.parse('ru', ru);
|
||||||
|
localization.parse('zh', zh);
|
||||||
|
|
||||||
function renderReactApplication() {
|
function renderReactApplication() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -45,5 +58,8 @@ function renderReactApplication() {
|
||||||
}
|
}
|
||||||
|
|
||||||
userSettings.loadUserSettings().then(() => {
|
userSettings.loadUserSettings().then(() => {
|
||||||
applyUrlCommands(renderReactApplication);
|
applyUrlCommands((parameters: any) => {
|
||||||
|
initializationI18n(parameters.lang || parameters.language);
|
||||||
|
renderReactApplication();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,6 +67,8 @@ hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select, td {
|
input, select, td {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,4 +78,4 @@ td {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
border: 0
|
border: 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,11 @@ function Recommendations({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title title={title}/>
|
<Title title={title}/>
|
||||||
<div className={className}>
|
<div
|
||||||
|
className={className}
|
||||||
|
onTouchStart={(event) => event.stopPropagation()}
|
||||||
|
onMouseDown={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
{cards}
|
{cards}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -45,6 +45,8 @@ function Table({
|
||||||
<div
|
<div
|
||||||
ref={refTable}
|
ref={refTable}
|
||||||
className={`${style.table_wrapper} scroll_x`}
|
className={`${style.table_wrapper} scroll_x`}
|
||||||
|
onTouchStart={(event) => event.stopPropagation()}
|
||||||
|
onMouseDown={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className={`${style.table}`}>
|
<div className={`${style.table}`}>
|
||||||
<Header
|
<Header
|
||||||
|
|
|
@ -38,6 +38,8 @@ function Tempo({
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={customStyle}
|
style={customStyle}
|
||||||
className={`${style.tempo_wrapper} scroll_x`}
|
className={`${style.tempo_wrapper} scroll_x`}
|
||||||
|
onTouchStart={(event) => event.stopPropagation()}
|
||||||
|
onMouseDown={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className={style.tempo}>
|
<div className={style.tempo}>
|
||||||
{columns}
|
{columns}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { ChangeEvent } from 'react';
|
import React, { ChangeEvent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
||||||
import style from '../styles/index.module.scss';
|
import style from '../styles/index.module.scss';
|
||||||
|
@ -20,6 +21,7 @@ function UiKitInputNumber({
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
onChange,
|
||||||
}: IUiKitSelectProps) {
|
}: IUiKitSelectProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Wrapper
|
<Wrapper
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -31,7 +33,7 @@ function UiKitInputNumber({
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder ? t(placeholder) : ''}
|
||||||
className={style.ui_kit_common}
|
className={style.ui_kit_common}
|
||||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (onChange) onChange(parseInt(event.target.value, 10) || 0);
|
if (onChange) onChange(parseInt(event.target.value, 10) || 0);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { ChangeEvent } from 'react';
|
||||||
|
|
||||||
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
||||||
import style from '../styles/index.module.scss';
|
import style from '../styles/index.module.scss';
|
||||||
|
import styleSelect from '../styles/select.module.scss';
|
||||||
|
|
||||||
interface IUiKitSelectProps extends IUiKitWrapperProps {
|
interface IUiKitSelectProps extends IUiKitWrapperProps {
|
||||||
value: any;
|
value: any;
|
||||||
|
@ -46,7 +47,7 @@ function UiKitSelect({
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
<select
|
<select
|
||||||
className={`${style.ui_kit_common} ${style.ui_kit_select}`}
|
className={`${style.ui_kit_common} ${styleSelect.ui_kit_select} ${className || ''}`}
|
||||||
onChange={(event: ChangeEvent<HTMLSelectElement>) => {
|
onChange={(event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
const selectedValue = event.target.value;
|
const selectedValue = event.target.value;
|
||||||
const formattedValue = selectedValue !== 'null'
|
const formattedValue = selectedValue !== 'null'
|
||||||
|
|
|
@ -4,10 +4,13 @@ import { IUiKitWrapperProps } from './Wrapper';
|
||||||
import UiKitButton from './Button';
|
import UiKitButton from './Button';
|
||||||
import UiKitSelect from './Select';
|
import UiKitSelect from './Select';
|
||||||
|
|
||||||
|
import style from '../styles/select.module.scss';
|
||||||
|
|
||||||
interface IUiKitSelectWithButtonsProps extends IUiKitWrapperProps {
|
interface IUiKitSelectWithButtonsProps extends IUiKitWrapperProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
value: any;
|
value: any;
|
||||||
options: any[];
|
options: any[];
|
||||||
|
reverse: boolean;
|
||||||
onChange: Function;
|
onChange: Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,17 +18,25 @@ function UiKitSelectWithButtons({
|
||||||
className,
|
className,
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
|
reverse,
|
||||||
onChange,
|
onChange,
|
||||||
}: IUiKitSelectWithButtonsProps) {
|
}: IUiKitSelectWithButtonsProps) {
|
||||||
let index = options.map((item: any) => item.id).indexOf(value);
|
let index = options.map((item: any) => item.id).indexOf(value);
|
||||||
if (index === -1) index = 0;
|
if (index === -1) index = 0;
|
||||||
|
|
||||||
|
const disabledPrev = index <= 0;
|
||||||
|
const disabledNext = index >= (options.length - 1);
|
||||||
|
const newPrevValue = options[index - 1]?.id;
|
||||||
|
const newNextValue = options[index + 1]?.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={`${style.ui_kit_select_with_buttons_wrapper} ${className || ''}`}>
|
||||||
<UiKitButton
|
<UiKitButton
|
||||||
mode="second"
|
mode="second"
|
||||||
disabled={index <= 0}
|
className={style.ui_kit_select_with_buttons_left}
|
||||||
|
disabled={reverse ? disabledNext : disabledPrev}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange(options[index - 1]?.id);
|
onChange(reverse ? newNextValue : newPrevValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
«
|
«
|
||||||
|
@ -33,23 +44,24 @@ function UiKitSelectWithButtons({
|
||||||
<UiKitSelect
|
<UiKitSelect
|
||||||
value={value}
|
value={value}
|
||||||
options={options}
|
options={options}
|
||||||
className={className}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<UiKitButton
|
<UiKitButton
|
||||||
mode="second"
|
mode="second"
|
||||||
disabled={index >= (options.length - 1)}
|
className={style.ui_kit_select_with_buttons_right}
|
||||||
|
disabled={reverse ? disabledPrev : disabledNext}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange(options[index + 1]?.id);
|
onChange(reverse ? newPrevValue : newNextValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
»
|
»
|
||||||
</UiKitButton>
|
</UiKitButton>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
UiKitSelectWithButtons.defaultProps = {
|
UiKitSelectWithButtons.defaultProps = {
|
||||||
|
reverse: false,
|
||||||
className: '',
|
className: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import style from '../styles/index.module.scss';
|
import style from '../styles/wrapper.module.scss';
|
||||||
|
|
||||||
export interface IUiKitWrapperProps {
|
export interface IUiKitWrapperProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
|
@ -22,53 +22,6 @@
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui_kit_wrapper + .ui_kit_wrapper {
|
|
||||||
vertical-align: top;
|
|
||||||
margin-top: var(--space-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_title,
|
|
||||||
.ui_kit_description,
|
|
||||||
.ui_kit_help,
|
|
||||||
.ui_kit_error,
|
|
||||||
.button {
|
|
||||||
font-weight: 100;
|
|
||||||
font-size: var(--font-xs);
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
line-height: 1.3;
|
|
||||||
text-decoration: none;
|
|
||||||
vertical-align: bottom;
|
|
||||||
color: var(--color-grey);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_title {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--color-black);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_title,
|
|
||||||
.ui_kit_description {
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_help,
|
|
||||||
.ui_kit_error {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_error {
|
|
||||||
color: var(--color-12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_select {
|
|
||||||
padding: 0 var(--space-xs) 0 var(--space-l);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui_kit_dialog {
|
.ui_kit_dialog {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 48px;
|
bottom: 48px;
|
||||||
|
|
27
src/ts/components/UiKit/styles/select.module.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@import 'src/styles/variables';
|
||||||
|
|
||||||
|
.ui_kit_select {
|
||||||
|
padding: 0 var(--space-xs) 0 var(--space-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_select_with_buttons {
|
||||||
|
&_wrapper {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_left {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_right {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
43
src/ts/components/UiKit/styles/wrapper.module.scss
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
@import 'src/styles/variables';
|
||||||
|
|
||||||
|
.ui_kit_wrapper + .ui_kit_wrapper {
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: var(--space-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_title,
|
||||||
|
.ui_kit_description,
|
||||||
|
.ui_kit_help,
|
||||||
|
.ui_kit_error {
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: bottom;
|
||||||
|
color: var(--color-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_title,
|
||||||
|
.ui_kit_description {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_help,
|
||||||
|
.ui_kit_error {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui_kit_error {
|
||||||
|
color: var(--color-12);
|
||||||
|
}
|
|
@ -58,8 +58,8 @@ export default function applyUrlCommands(callback: Function) {
|
||||||
|
|
||||||
const jsUrl = parameters.dump || parameters.log;
|
const jsUrl = parameters.dump || parameters.log;
|
||||||
if (jsUrl) {
|
if (jsUrl) {
|
||||||
loadJsDump(jsUrl, callback);
|
loadJsDump(jsUrl, () => callback(parameters));
|
||||||
} else {
|
} else {
|
||||||
callback();
|
callback(parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,26 @@ export function getDayPrefix(index:number) {
|
||||||
][index];
|
][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLangPrefix() {
|
||||||
|
// @ts-ignore
|
||||||
|
const code = window?.localization?.language || 'ru';
|
||||||
|
return {
|
||||||
|
ru: 'ru-RU',
|
||||||
|
en: 'en-EN',
|
||||||
|
zh: 'zh-ZH',
|
||||||
|
es: 'es-ES',
|
||||||
|
fr: 'fr-FR',
|
||||||
|
pt: 'pt-PT',
|
||||||
|
de: 'de-DE',
|
||||||
|
ja: 'ja-JA',
|
||||||
|
}[code] || 'ru-RU';
|
||||||
|
}
|
||||||
|
|
||||||
export function getDateByTimestamp(timestamp: string) {
|
export function getDateByTimestamp(timestamp: string) {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const day = date.getDay() - 1;
|
const day = date.getDay() - 1;
|
||||||
return [
|
return [
|
||||||
date.toLocaleString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }),
|
date.toLocaleString(getLangPrefix(), { day: 'numeric', month: 'long', year: 'numeric' }),
|
||||||
getDayName(day < 0 ? 6 : day),
|
getDayName(day < 0 ? 6 : day),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -50,7 +65,7 @@ export function getClearHTML(text: string) {
|
||||||
export function getDate(timestamp: string) {
|
export function getDate(timestamp: string) {
|
||||||
if (!timestamp) return '';
|
if (!timestamp) return '';
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toLocaleString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
|
return date.toLocaleString(getLangPrefix(), { day: 'numeric', month: 'long', year: 'numeric' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDateForExcel(timestamp: string) {
|
export function getDateForExcel(timestamp: string) {
|
||||||
|
@ -62,7 +77,7 @@ export function getDateForExcel(timestamp: string) {
|
||||||
export function getShortDate(timestamp: string) {
|
export function getShortDate(timestamp: string) {
|
||||||
if (!timestamp) return '';
|
if (!timestamp) return '';
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toLocaleString('ru-RU', { day: 'numeric', month: 'long' });
|
return date.toLocaleString(getLangPrefix(), { day: 'numeric', month: 'long' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShortTime(timestamp: string) {
|
export function getShortTime(timestamp: string) {
|
||||||
|
@ -72,7 +87,7 @@ export function getShortTime(timestamp: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMoney(value: number, options?: any) {
|
export function getMoney(value: number, options?: any) {
|
||||||
return (value || 0).toLocaleString('ru-RU', {
|
return (value || 0).toLocaleString(getLangPrefix(), {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: settingsStore?.currency || 'USD',
|
currency: settingsStore?.currency || 'USD',
|
||||||
currencyDisplay: 'symbol',
|
currencyDisplay: 'symbol',
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
import ru from '../translations/ru';
|
import de from '../translations/de';
|
||||||
import en from '../translations/en';
|
import en from '../translations/en';
|
||||||
|
import es from '../translations/es';
|
||||||
|
import fr from '../translations/fr';
|
||||||
|
import ja from '../translations/ja';
|
||||||
|
import pt from '../translations/pt';
|
||||||
|
import ru from '../translations/ru';
|
||||||
|
import zh from '../translations/zh';
|
||||||
|
|
||||||
function getJsonFromString(text: string) {
|
function getJsonFromString(text: string) {
|
||||||
return text
|
return text
|
||||||
|
@ -27,14 +33,22 @@ function getTranslationWrapper(translation: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
i18next.use(initReactI18next).init({
|
export default function initializationI18n(defaultLanguage?: string) {
|
||||||
lng: 'ru', // if you're using a language detector, do not define the lng option
|
i18next.use(initReactI18next).init({
|
||||||
debug: false,
|
lng: defaultLanguage || 'ru', // if you're using a language detector, do not define the lng option
|
||||||
resources: {
|
debug: false,
|
||||||
ru: getTranslationWrapper(ru),
|
resources: {
|
||||||
en: getTranslationWrapper(en),
|
de: getTranslationWrapper(de),
|
||||||
},
|
en: getTranslationWrapper(en),
|
||||||
// if you see an error like: "Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz"
|
es: getTranslationWrapper(es),
|
||||||
// set returnNull to false (and also in the i18next.d.ts options)
|
fr: getTranslationWrapper(fr),
|
||||||
// returnNull: false,
|
ja: getTranslationWrapper(ja),
|
||||||
});
|
pt: getTranslationWrapper(pt),
|
||||||
|
ru: getTranslationWrapper(ru),
|
||||||
|
zh: getTranslationWrapper(zh),
|
||||||
|
},
|
||||||
|
// if you see an error like: "Argument of type 'DefaultTFuncReturn' is not assignable to parameter of type xyz"
|
||||||
|
// set returnNull to false (and also in the i18next.d.ts options)
|
||||||
|
// returnNull: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useParams } from 'react-router-dom';
|
||||||
import isMobile from 'ts/helpers/isMobile';
|
import isMobile from 'ts/helpers/isMobile';
|
||||||
|
|
||||||
import { TEAM, PERSON } from '../helpers/menu';
|
import { TEAM, PERSON } from '../helpers/menu';
|
||||||
|
import PageSwiper from './Swiper';
|
||||||
import style from '../styles/slider.module.scss';
|
import style from '../styles/slider.module.scss';
|
||||||
|
|
||||||
interface ISectionSliderProps {
|
interface ISectionSliderProps {
|
||||||
|
@ -56,6 +57,11 @@ function MobileView({ getViewById }: ISectionSliderProps) {
|
||||||
|
|
||||||
if (!currentView.length) {
|
if (!currentView.length) {
|
||||||
return getViewById(page);
|
return getViewById(page);
|
||||||
|
return (
|
||||||
|
<PageSwiper>
|
||||||
|
{getViewById(page)}
|
||||||
|
</PageSwiper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [viewName, className] = currentView;
|
const [viewName, className] = currentView;
|
||||||
|
|
132
src/ts/pages/PageWrapper/components/Swiper.tsx
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { TEAM, PERSON } from '../helpers/menu';
|
||||||
|
|
||||||
|
function getXByEvent(event: any) {
|
||||||
|
return event.screenX || event?.touches?.[0]?.screenX || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYByEvent(event: any) {
|
||||||
|
return event.screenY || event?.touches?.[0]?.screenY || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLeftWithLimits(moveEvent: any) {
|
||||||
|
if (moveEvent?.top > 15
|
||||||
|
|| moveEvent?.top < -15) return 0;
|
||||||
|
|
||||||
|
const size = Math.abs(moveEvent?.left || 0);
|
||||||
|
const direction = size / (moveEvent?.left || 0);
|
||||||
|
return size > 20
|
||||||
|
? (Math.min(size, 40) * direction)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSwipeEvents(
|
||||||
|
element: any,
|
||||||
|
urlParameters: any,
|
||||||
|
setMoveEvent: Function,
|
||||||
|
navigate: Function,
|
||||||
|
) {
|
||||||
|
let top = 0;
|
||||||
|
let left = 0;
|
||||||
|
let firstPositionX = 0;
|
||||||
|
let firstPositionY = 0;
|
||||||
|
let overflowX: any = undefined;
|
||||||
|
|
||||||
|
function onStart(event: any) {
|
||||||
|
setMoveEvent({ top: 0, left: 0 });
|
||||||
|
top = 0;
|
||||||
|
left = 0;
|
||||||
|
firstPositionY = getYByEvent(event);
|
||||||
|
firstPositionX = getXByEvent(event);
|
||||||
|
overflowX = document.body.style.overflowX;
|
||||||
|
document.body.style.overflowX = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMove(event: any) {
|
||||||
|
if (!firstPositionX) return;
|
||||||
|
top = getYByEvent(event) - firstPositionY;
|
||||||
|
left = getXByEvent(event) - firstPositionX;
|
||||||
|
setMoveEvent({ top, left });
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnd() {
|
||||||
|
if (!firstPositionX) return;
|
||||||
|
const { type, page, userId } = urlParameters;
|
||||||
|
const list = type === 'team' ? TEAM : PERSON;
|
||||||
|
const pages = list.map((item) => item.id).filter((value) => value);
|
||||||
|
const index = pages.indexOf(page || 'total');
|
||||||
|
const getUrl = type === 'team'
|
||||||
|
? (newPage?: string) => `/team/${newPage || 'total'}`
|
||||||
|
: (newPage?: string) => `/person/${newPage || 'total'}/${userId || '0'}`;
|
||||||
|
const hasSmallShiftByY = Math.abs(top) < 15;
|
||||||
|
|
||||||
|
if (left > 30 && index > 0 && hasSmallShiftByY) {
|
||||||
|
navigate(getUrl(pages[index - 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNext = index >= 0 && index < (pages.length - 1);
|
||||||
|
if (left < -30 && hasNext && hasSmallShiftByY) {
|
||||||
|
navigate(getUrl(pages[index + 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.style.overflowX = overflowX;
|
||||||
|
setMoveEvent(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancel() {
|
||||||
|
if (!firstPositionX) return;
|
||||||
|
top = 0;
|
||||||
|
left = 0;
|
||||||
|
document.body.style.overflowX = overflowX;
|
||||||
|
setMoveEvent(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// const events = ['mousedown', 'mousemove', 'mouseup', 'mouseleave'];
|
||||||
|
const events = ['touchstart', 'touchmove', 'touchend', 'touchcancel'];
|
||||||
|
|
||||||
|
element.addEventListener(events[0], onStart);
|
||||||
|
element.addEventListener(events[1], onMove);
|
||||||
|
element.addEventListener(events[2], onEnd);
|
||||||
|
element.addEventListener(events[3], onCancel);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
element.removeEventListener(events[0], onStart);
|
||||||
|
element.removeEventListener(events[1], onMove);
|
||||||
|
element.removeEventListener(events[2], onEnd);
|
||||||
|
element.removeEventListener(events[3], onCancel);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPageSwiperProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PageSwiper({ children }: IPageSwiperProps) {
|
||||||
|
const { type, page, userId } = useParams<any>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
|
||||||
|
const [moveEvent, setMoveEvent] = useState<any>(null);
|
||||||
|
const left = getLeftWithLimits(moveEvent);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref?.current;
|
||||||
|
if (!element) return;
|
||||||
|
return addSwipeEvents(
|
||||||
|
element,
|
||||||
|
{ type, page, userId },
|
||||||
|
setMoveEvent,
|
||||||
|
navigate,
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{ position: 'relative', left }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -29,8 +29,8 @@ function getMenu(navigate: Function): any[] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'print',
|
id: 'print',
|
||||||
title: 'sidebar.buttons.print',
|
title: 'sidebar.buttons.share',
|
||||||
icon: './assets/menu/print.svg',
|
icon: './assets/menu/share.svg',
|
||||||
onClick() {
|
onClick() {
|
||||||
navigator.share({
|
navigator.share({
|
||||||
title: localization.get('common.title'),
|
title: localization.get('common.title'),
|
||||||
|
@ -41,8 +41,8 @@ function getMenu(navigate: Function): any[] {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
title: 'sidebar.buttons.settings',
|
title: 'sidebar.buttons.logout',
|
||||||
icon: './assets/menu/setting.svg',
|
icon: './assets/menu/logout.svg',
|
||||||
onClick() {
|
onClick() {
|
||||||
confirm.open({
|
confirm.open({
|
||||||
title: 'Вы уверены что хотите выйти?',
|
title: 'Вы уверены что хотите выйти?',
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import Select from 'ts/components/UiKit/components/Select';
|
||||||
import Buttons from 'ts/pages/Settings/components/Buttons';
|
import Buttons from 'ts/pages/Settings/components/Buttons';
|
||||||
import settingsForm from 'ts/pages/Settings/store/Form';
|
import settingsForm from 'ts/pages/Settings/store/Form';
|
||||||
|
import localization from 'ts/helpers/Localization';
|
||||||
|
|
||||||
import Title from './Title';
|
import Title from './Title';
|
||||||
import Filters from './Filters';
|
import Filters from './Filters';
|
||||||
|
@ -12,9 +14,9 @@ import printStore from '../../store/Print';
|
||||||
import style from '../../styles/header.module.scss';
|
import style from '../../styles/header.module.scss';
|
||||||
|
|
||||||
const Header = observer((): React.ReactElement | null => {
|
const Header = observer((): React.ReactElement | null => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={style.header}>
|
<header className={style.header}>
|
||||||
|
@ -24,6 +26,24 @@ const Header = observer((): React.ReactElement | null => {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Filters/>
|
<Filters/>
|
||||||
|
<Select
|
||||||
|
className={style.header_lang}
|
||||||
|
value={localization.language}
|
||||||
|
options={[
|
||||||
|
{ id: 'ru', title: 'RU' },
|
||||||
|
{ id: 'en', title: 'EN' },
|
||||||
|
{ id: 'zh', title: 'ZH' },
|
||||||
|
{ id: 'es', title: 'ES' },
|
||||||
|
{ id: 'fr', title: 'FR' },
|
||||||
|
{ id: 'pt', title: 'PT' },
|
||||||
|
{ id: 'de', title: 'DE' },
|
||||||
|
{ id: 'ja', title: 'JA' },
|
||||||
|
]}
|
||||||
|
onChange={(item: any, id: string) => {
|
||||||
|
localization.language = id;
|
||||||
|
i18n.changeLanguage(id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<img
|
<img
|
||||||
title={t('sidebar.buttons.print')}
|
title={t('sidebar.buttons.print')}
|
||||||
className={style.header_print}
|
className={style.header_print}
|
||||||
|
|
|
@ -42,13 +42,15 @@
|
||||||
border-radius: var(--border-radius-m);
|
border-radius: var(--border-radius-m);
|
||||||
--temp-color: var(--color-grey);
|
--temp-color: var(--color-grey);
|
||||||
|
|
||||||
&:hover,
|
&:first-child:hover,
|
||||||
|
&:nth-child(4n+2):hover,
|
||||||
&_selected {
|
&_selected {
|
||||||
background-color: #35353F;
|
background-color: #35353F;
|
||||||
--temp-color: white;
|
--temp-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:first-child:active,
|
||||||
|
&:nth-child(4n+2):active {
|
||||||
background-color: #45454F;
|
background-color: #45454F;
|
||||||
--temp-color: white;
|
--temp-color: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&_print,
|
&_print,
|
||||||
|
&_lang,
|
||||||
&_setting {
|
&_setting {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 4px 24px 0 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&_setting {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
&_print {
|
&_print {
|
||||||
margin-right: 24px;
|
margin: 4px 24px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_lang {
|
||||||
|
width: 50px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 24px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_with_tab {
|
&_with_tab {
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--color-black);
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_main,
|
&_main,
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
&_main_mobile {
|
&_main_mobile {
|
||||||
width: calc(100vw - 240px);
|
width: calc(100vw - 240px);
|
||||||
|
padding: 24px 24px 82px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,17 @@ const Month = observer(({ user }: IPersonCommonProps): React.ReactElement => {
|
||||||
const max = statistic.commitsByTimestampCounter.max;
|
const max = statistic.commitsByTimestampCounter.max;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper template="table">
|
<>
|
||||||
<YearChart
|
<br/>
|
||||||
showEvents={false}
|
<PageWrapper template="table">
|
||||||
maxCommits={max}
|
<YearChart
|
||||||
authors={[author]}
|
showEvents={false}
|
||||||
wordDays={statistic.allCommitsByTimestamp}
|
maxCommits={max}
|
||||||
/>
|
authors={[author]}
|
||||||
</PageWrapper>
|
wordDays={statistic.allCommitsByTimestamp}
|
||||||
|
/>
|
||||||
|
</PageWrapper>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
import { IPagination } from 'ts/interfaces/Pagination';
|
import { IPagination } from 'ts/interfaces/Pagination';
|
||||||
import dataGripStore from 'ts/store/DataGrip';
|
import dataGripStore from 'ts/store/DataGrip';
|
||||||
import { getShortDateRange } from 'ts/helpers/formatter';
|
|
||||||
|
|
||||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
|
||||||
import PageWrapper from 'ts/components/Page/wrapper';
|
import PageWrapper from 'ts/components/Page/wrapper';
|
||||||
import DataLoader from 'ts/components/DataLoader';
|
import DataLoader from 'ts/components/DataLoader';
|
||||||
import Pagination from 'ts/components/DataLoader/components/Pagination';
|
import Pagination from 'ts/components/DataLoader/components/Pagination';
|
||||||
|
@ -13,9 +11,6 @@ import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
|
||||||
import NothingFound from 'ts/components/NothingFound';
|
import NothingFound from 'ts/components/NothingFound';
|
||||||
import TempoChart from 'ts/components/Tempo';
|
import TempoChart from 'ts/components/Tempo';
|
||||||
|
|
||||||
import uiKitStyle from 'ts/components/UiKit/styles/index.module.scss';
|
|
||||||
import style from 'ts/pages/Team/styles/filters.module.scss';
|
|
||||||
|
|
||||||
import IPersonCommonProps from '../interfaces/CommonProps';
|
import IPersonCommonProps from '../interfaces/CommonProps';
|
||||||
|
|
||||||
interface ITempoViewProps {
|
interface ITempoViewProps {
|
||||||
|
@ -41,58 +36,24 @@ function getPartOfData(filters: any, rows: any[]) {
|
||||||
return rows.filter((row: any) => (row.week === filters.week)).slice(0, 7);
|
return rows.filter((row: any) => (row.week === filters.week)).slice(0, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tempo = observer(({ user }: IPersonCommonProps): React.ReactElement => {
|
const Tempo = observer(({ user, filters }: IPersonCommonProps): React.ReactElement => {
|
||||||
const author = user;
|
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||||
|
|
||||||
const rows = dataGripStore.dataGrip.timestamp.statisticByAuthor[author.author]?.allCommitsByTimestamp || [];
|
|
||||||
const firstIndex = rows.length - 1;
|
|
||||||
const firstPoint = rows[firstIndex];
|
|
||||||
|
|
||||||
const [week, setWeek] = useState<number>(firstPoint.week);
|
|
||||||
|
|
||||||
if (!rows?.length) return (<NothingFound />);
|
if (!rows?.length) return (<NothingFound />);
|
||||||
const partOfData = getPartOfData({ week, user: author.author }, rows);
|
|
||||||
const firstWeekDay = partOfData[0];
|
|
||||||
const lastWeekDay = partOfData[partOfData.length - 1];
|
|
||||||
|
|
||||||
|
const partOfData = getPartOfData({ week: filters.week, user: user.author }, rows);
|
||||||
if (!partOfData?.length) return (<NothingFound />);
|
if (!partOfData?.length) return (<NothingFound />);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageWrapper>
|
<br/>
|
||||||
<div className={style.tempo_filters}>
|
|
||||||
<UiKitButton
|
|
||||||
mode="second"
|
|
||||||
disabled={week === 1}
|
|
||||||
onClick={() => {
|
|
||||||
setWeek(week - 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</UiKitButton>
|
|
||||||
<div className={`${uiKitStyle.ui_kit_common} ${style.tempo_filters_date_range}`}>
|
|
||||||
{getShortDateRange({
|
|
||||||
from: firstWeekDay.timestamp,
|
|
||||||
to: lastWeekDay.timestamp,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<UiKitButton
|
|
||||||
mode="second"
|
|
||||||
disabled={week === firstPoint.week}
|
|
||||||
onClick={() => {
|
|
||||||
setWeek(week + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</UiKitButton>
|
|
||||||
</div>
|
|
||||||
</PageWrapper>
|
|
||||||
<PageWrapper template="table">
|
<PageWrapper template="table">
|
||||||
<DataLoader
|
<DataLoader
|
||||||
to="response"
|
to="response"
|
||||||
loader={() => getFakeLoader({ content: partOfData })}
|
loader={() => getFakeLoader({ content: partOfData })}
|
||||||
watch={week}
|
watch={JSON.stringify(filters)}
|
||||||
>
|
>
|
||||||
<TempoView user={author.author}/>
|
<TempoView user={user.author}/>
|
||||||
<Pagination />
|
<Pagination />
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
import dataGripStore from 'ts/store/DataGrip';
|
import dataGripStore from 'ts/store/DataGrip';
|
||||||
import UiKitSelect from 'ts/components/UiKit/components/Select';
|
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
import style from 'ts/pages/Team/styles/filters.module.scss';
|
||||||
|
import { getFormattedWeeks } from 'ts/pages/Team/components/TempoFilters';
|
||||||
|
|
||||||
import style from '../styles/index.module.scss';
|
interface IUserSelectProps {
|
||||||
|
filters: any,
|
||||||
|
onChange: Function,
|
||||||
|
}
|
||||||
|
|
||||||
const UserSelect = observer((): React.ReactElement => {
|
const UserSelect = observer(({
|
||||||
|
filters,
|
||||||
|
onChange,
|
||||||
|
}: IUserSelectProps): React.ReactElement => {
|
||||||
const { type, page, userId } = useParams<any>();
|
const { type, page, userId } = useParams<any>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -16,34 +23,32 @@ const UserSelect = observer((): React.ReactElement => {
|
||||||
const authors = dataGripStore.dataGrip.author.list;
|
const authors = dataGripStore.dataGrip.author.list;
|
||||||
const options = authors.map((title: string, id: number) => ({ id, title }));
|
const options = authors.map((title: string, id: number) => ({ id, title }));
|
||||||
|
|
||||||
|
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||||
|
const weeks = useMemo(() => getFormattedWeeks(rows), [rows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.user_select}>
|
<div className={style.table_filters}>
|
||||||
<UiKitButton
|
<SelectWithButtons
|
||||||
mode="second"
|
title="page.team.tree.filters.author"
|
||||||
disabled={formattedUserId <= 0}
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/${type}/${page}/${formattedUserId - 1}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</UiKitButton>
|
|
||||||
<UiKitSelect
|
|
||||||
value={formattedUserId}
|
value={formattedUserId}
|
||||||
|
className={style.table_filters_item}
|
||||||
options={options}
|
options={options}
|
||||||
className={style.user_name}
|
onChange={(newUserId: number) => {
|
||||||
onChange={(newUserId: string) => {
|
|
||||||
navigate(`/${type}/${page}/${newUserId}`);
|
navigate(`/${type}/${page}/${newUserId}`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<UiKitButton
|
{page === 'day' ? (
|
||||||
mode="second"
|
<SelectWithButtons
|
||||||
disabled={formattedUserId >= (authors.length - 1)}
|
reverse
|
||||||
onClick={() => {
|
title="page.team.tree.filters.author"
|
||||||
navigate(`/${type}/${page}/${formattedUserId + 1}`);
|
value={filters?.week || rows[rows.length - 1].week}
|
||||||
}}
|
className={style.table_filters_item}
|
||||||
>
|
options={weeks.reverse()}
|
||||||
»
|
onChange={(week: number) => {
|
||||||
</UiKitButton>
|
onChange({ ...filters, week });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -132,6 +132,7 @@ const Week = observer(({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<br/>
|
||||||
<Recommendations
|
<Recommendations
|
||||||
mode={mode}
|
mode={mode}
|
||||||
recommendations={recommendations}
|
recommendations={recommendations}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -26,7 +26,7 @@ interface IPersonProps {
|
||||||
userId?: string | number;
|
userId?: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getViewByIdByUser(user: any) {
|
function getViewByIdByUser(user: any, filters: any) {
|
||||||
return function getViewById(page?: string) {
|
return function getViewById(page?: string) {
|
||||||
const mode = printStore.processing ? 'print' : undefined;
|
const mode = printStore.processing ? 'print' : undefined;
|
||||||
if (page === 'total') return <Total user={user}/>;
|
if (page === 'total') return <Total user={user}/>;
|
||||||
|
@ -48,7 +48,12 @@ function getViewByIdByUser(user: any) {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (page === 'speed') return <Speed user={user}/>;
|
if (page === 'speed') return <Speed user={user}/>;
|
||||||
if (page === 'day') return <Tempo user={user}/>;
|
if (page === 'day') return (
|
||||||
|
<Tempo
|
||||||
|
user={user}
|
||||||
|
filters={filters}
|
||||||
|
/>
|
||||||
|
);
|
||||||
if (page === 'print') return <Print user={user}/>;
|
if (page === 'print') return <Print user={user}/>;
|
||||||
return <Total user={user}/>;
|
return <Total user={user}/>;
|
||||||
};
|
};
|
||||||
|
@ -59,16 +64,26 @@ const Person = observer(({
|
||||||
}: IPersonProps) => {
|
}: IPersonProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { type, page, userId: userIdFromUrl } = useParams<any>();
|
const { type, page, userId: userIdFromUrl } = useParams<any>();
|
||||||
|
|
||||||
|
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||||
|
const defaultWeek = rows.length
|
||||||
|
? rows[rows.length - 1].week
|
||||||
|
: 0;
|
||||||
|
const [filters, setFilters] = useState<any>({ week: defaultWeek });
|
||||||
|
|
||||||
const user = dataGripStore.dataGrip.author.statistic[userId || userIdFromUrl || 0];
|
const user = dataGripStore.dataGrip.author.statistic[userId || userIdFromUrl || 0];
|
||||||
if (type !== 'person' || !user) return null;
|
if (type !== 'person' || !user) return null;
|
||||||
|
|
||||||
const getViewById = getViewByIdByUser(user);
|
const getViewById = getViewByIdByUser(user, filters);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{page !== 'print' && (
|
{page !== 'print' && (
|
||||||
<>
|
<>
|
||||||
<Title title={t('common.filters')} />
|
<Title title={t('common.filters')} />
|
||||||
<UserSelect />
|
<UserSelect
|
||||||
|
filters={filters}
|
||||||
|
onChange={setFilters}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<SectionSlider getViewById={getViewById} />
|
<SectionSlider getViewById={getViewById} />
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export default interface IPersonCommonProps {
|
export default interface IPersonCommonProps {
|
||||||
user: any;
|
user: any;
|
||||||
|
filters?: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ const Common = observer((): React.ReactElement | null => {
|
||||||
const [language, setLanguage] = useState<string>(localization.language);
|
const [language, setLanguage] = useState<string>(localization.language);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(language);
|
|
||||||
i18n.changeLanguage(language);
|
i18n.changeLanguage(language);
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
|
@ -39,6 +38,12 @@ const Common = observer((): React.ReactElement | null => {
|
||||||
options={[
|
options={[
|
||||||
{ id: 'ru', title: 'Русский' },
|
{ id: 'ru', title: 'Русский' },
|
||||||
{ id: 'en', title: 'English' },
|
{ id: 'en', title: 'English' },
|
||||||
|
{ id: 'zh', title: '中文' },
|
||||||
|
{ id: 'es', title: 'Español' },
|
||||||
|
{ id: 'fr', title: 'Français' },
|
||||||
|
{ id: 'pt', title: 'Português' },
|
||||||
|
{ id: 'de', title: 'Deutsch' },
|
||||||
|
{ id: 'ja', title: '日本語' },
|
||||||
]}
|
]}
|
||||||
onChange={(item: any, id: string) => {
|
onChange={(item: any, id: string) => {
|
||||||
localization.language = id;
|
localization.language = id;
|
||||||
|
|
|
@ -42,8 +42,8 @@ export default function getEmptySettings(): IUserSetting {
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
defaultSalary: {
|
defaultSalary: {
|
||||||
value: 180000,
|
value: 3000,
|
||||||
currency: 'RUB',
|
currency: 'USD',
|
||||||
workDaysInYear: 247,
|
workDaysInYear: 247,
|
||||||
vacationDaysInYear: 28,
|
vacationDaysInYear: 28,
|
||||||
workDaysInWeek: [1, 1, 1, 1, 1, 0, 0],
|
workDaysInWeek: [1, 1, 1, 1, 1, 0, 0],
|
||||||
|
|
|
@ -10,7 +10,7 @@ class Settings {
|
||||||
|
|
||||||
workDaysInMonth: number = 22;
|
workDaysInMonth: number = 22;
|
||||||
|
|
||||||
salaryInDay: number = 180000 / 22;
|
salaryInDay: number = 3000 / 22;
|
||||||
|
|
||||||
update(customSettings?: IUserSetting) {
|
update(customSettings?: IUserSetting) {
|
||||||
this.customSettings = customSettings || getEmptySettings();
|
this.customSettings = customSettings || getEmptySettings();
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { observer } from 'mobx-react-lite';
|
||||||
|
|
||||||
import { IPagination } from 'ts/interfaces/Pagination';
|
import { IPagination } from 'ts/interfaces/Pagination';
|
||||||
import dataGripStore from 'ts/store/DataGrip';
|
import dataGripStore from 'ts/store/DataGrip';
|
||||||
import { getShortDateRange } from 'ts/helpers/formatter';
|
|
||||||
|
|
||||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
|
||||||
import UiKitSelect from 'ts/components/UiKit/components/Select';
|
|
||||||
import PageWrapper from 'ts/components/Page/wrapper';
|
import PageWrapper from 'ts/components/Page/wrapper';
|
||||||
import DataLoader from 'ts/components/DataLoader';
|
import DataLoader from 'ts/components/DataLoader';
|
||||||
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
|
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
|
||||||
|
@ -14,8 +11,7 @@ import NothingFound from 'ts/components/NothingFound';
|
||||||
import TempoChart from 'ts/components/Tempo';
|
import TempoChart from 'ts/components/Tempo';
|
||||||
import Title from 'ts/components/Title';
|
import Title from 'ts/components/Title';
|
||||||
|
|
||||||
import uiKitStyle from 'ts/components/UiKit/styles/index.module.scss';
|
import TempoFilters from './TempoFilters';
|
||||||
import style from '../styles/filters.module.scss';
|
|
||||||
|
|
||||||
interface ITempoViewProps {
|
interface ITempoViewProps {
|
||||||
order: string[];
|
order: string[];
|
||||||
|
@ -44,83 +40,38 @@ function getPartOfData(filters: any, rows: any[]) {
|
||||||
|
|
||||||
const Tempo = observer((): React.ReactElement => {
|
const Tempo = observer((): React.ReactElement => {
|
||||||
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||||
const order = dataGripStore.dataGrip.author.list || [];
|
const users = dataGripStore.dataGrip.author.list || [];
|
||||||
const firstIndex = rows.length - 1;
|
const firstIndex = rows.length - 1;
|
||||||
const firstPoint = rows[firstIndex];
|
const firstPoint = rows[firstIndex];
|
||||||
|
|
||||||
const [week, setWeek] = useState<number>(firstPoint.week);
|
const [filters, setFilters] = useState<any>({ week: firstPoint.week });
|
||||||
const [user, setUser] = useState<string>('');
|
const user = filters.user
|
||||||
|
? users[filters.user - 1]
|
||||||
|
: '';
|
||||||
|
|
||||||
if (!rows?.length) return (<NothingFound />);
|
if (!rows?.length) return (<NothingFound />);
|
||||||
const partOfData = getPartOfData({ week, user }, rows);
|
|
||||||
const firstWeekDay = partOfData[0];
|
|
||||||
const lastWeekDay = partOfData[partOfData.length - 1];
|
|
||||||
|
|
||||||
|
const partOfData = getPartOfData({ week: filters.week, user }, rows);
|
||||||
if (!partOfData?.length) return (<NothingFound />);
|
if (!partOfData?.length) return (<NothingFound />);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title title="common.filters" />
|
<Title title="common.filters" />
|
||||||
<PageWrapper>
|
<PageWrapper>
|
||||||
<div className={style.tempo_filters}>
|
<TempoFilters
|
||||||
<UiKitButton
|
filters={filters}
|
||||||
mode="second"
|
onChange={setFilters}
|
||||||
disabled={week === 1}
|
/>
|
||||||
onClick={() => {
|
|
||||||
setWeek(week - 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</UiKitButton>
|
|
||||||
<div className={`${uiKitStyle.ui_kit_common} ${style.tempo_filters_date_range}`}>
|
|
||||||
{getShortDateRange({
|
|
||||||
from: firstWeekDay.timestamp,
|
|
||||||
to: lastWeekDay.timestamp,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<UiKitButton
|
|
||||||
mode="second"
|
|
||||||
disabled={week === firstPoint.week}
|
|
||||||
onClick={() => {
|
|
||||||
setWeek(week + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</UiKitButton>
|
|
||||||
|
|
||||||
<UiKitButton
|
|
||||||
mode="second"
|
|
||||||
onClick={() => {
|
|
||||||
setUser(order[order.indexOf(user) - 1]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
«
|
|
||||||
</UiKitButton>
|
|
||||||
<UiKitSelect
|
|
||||||
className={style.tempo_filters_user}
|
|
||||||
value={user}
|
|
||||||
options={[ '', ...dataGripStore.dataGrip.author.list]}
|
|
||||||
onChange={(id: number, name: string) => {
|
|
||||||
setUser(name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<UiKitButton
|
|
||||||
mode="second"
|
|
||||||
onClick={() => {
|
|
||||||
setUser(order[order.indexOf(user) + 1]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
»
|
|
||||||
</UiKitButton>
|
|
||||||
</div>
|
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
|
<br/>
|
||||||
<PageWrapper template="table">
|
<PageWrapper template="table">
|
||||||
<DataLoader
|
<DataLoader
|
||||||
to="response"
|
to="response"
|
||||||
loader={() => getFakeLoader({ content: partOfData })}
|
loader={() => getFakeLoader({ content: partOfData })}
|
||||||
watch={`${week}${user}`}
|
watch={JSON.stringify(filters)}
|
||||||
>
|
>
|
||||||
<TempoView
|
<TempoView
|
||||||
order={order}
|
order={users}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
|
|
83
src/ts/pages/Team/components/TempoFilters.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||||
|
import { getShortDateRange } from 'ts/helpers/formatter';
|
||||||
|
import dataGripStore from 'ts/store/DataGrip';
|
||||||
|
|
||||||
|
import style from '../styles/filters.module.scss';
|
||||||
|
|
||||||
|
export function getFormattedWeeks(rows: any[]) {
|
||||||
|
const groups = (rows || []).reduce((group: any, row: any) => {
|
||||||
|
if (!group[row.week]) group[row.week] = [];
|
||||||
|
group[row.week].push(row);
|
||||||
|
return group;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return Object.entries(groups).map((item: any) => {
|
||||||
|
const firstDay = item[1][0];
|
||||||
|
const lastDay = item[1][(item[1].length - 1)];
|
||||||
|
return {
|
||||||
|
id: firstDay.week,
|
||||||
|
days: item[1],
|
||||||
|
title: getShortDateRange({
|
||||||
|
from: firstDay.timestamp,
|
||||||
|
to: lastDay.timestamp,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormattedUsers(rows: any[], t: Function) {
|
||||||
|
const options = rows.map((title: string, id: number) => ({ id: id + 1, title }));
|
||||||
|
options.unshift({ id: 0, title: t('page.team.tree.filters.all') });
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ITempoFiltersProps {
|
||||||
|
filters: {
|
||||||
|
week?: number;
|
||||||
|
user?: number;
|
||||||
|
};
|
||||||
|
onChange: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TempoFilters = observer(({
|
||||||
|
filters,
|
||||||
|
onChange,
|
||||||
|
}: ITempoFiltersProps): React.ReactElement => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||||
|
const weeks = useMemo(() => getFormattedWeeks(rows), [rows]);
|
||||||
|
|
||||||
|
const authors = dataGripStore.dataGrip.author.list;
|
||||||
|
const users = useMemo(() => getFormattedUsers(authors, t), [authors]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.table_filters}>
|
||||||
|
<SelectWithButtons
|
||||||
|
title="page.team.tree.filters.author"
|
||||||
|
value={filters.user}
|
||||||
|
className={style.table_filters_item}
|
||||||
|
options={users}
|
||||||
|
onChange={(user: number) => {
|
||||||
|
onChange({ ...filters, user });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SelectWithButtons
|
||||||
|
reverse
|
||||||
|
title="page.team.tree.filters.author"
|
||||||
|
value={filters.week || rows[rows.length - 1].week}
|
||||||
|
className={style.table_filters_item}
|
||||||
|
options={weeks.reverse()}
|
||||||
|
onChange={(week: number) => {
|
||||||
|
onChange({ ...filters, week });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TempoFilters;
|
|
@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import dataGripStore from 'ts/store/DataGrip';
|
import dataGripStore from 'ts/store/DataGrip';
|
||||||
import UiKitSelect from 'ts/components/UiKit/components/SelectWithButtons';
|
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||||
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
|
import UiKitInputNumber from 'ts/components/UiKit/components/InputNumber';
|
||||||
|
|
||||||
import treeStore from '../store/Tree';
|
import treeStore from '../store/Tree';
|
||||||
|
@ -16,21 +16,21 @@ const TreeFilters = observer((): React.ReactElement => {
|
||||||
options.unshift({ id: 0, title: t('page.team.tree.filters.all') });
|
options.unshift({ id: 0, title: t('page.team.tree.filters.all') });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={style.table_filters}>
|
||||||
<UiKitSelect
|
<SelectWithButtons
|
||||||
title="page.team.tree.filters.author"
|
title="page.team.tree.filters.author"
|
||||||
value={treeStore.authorId}
|
value={treeStore.authorId}
|
||||||
|
className={style.table_filters_item}
|
||||||
options={options}
|
options={options}
|
||||||
className={style.tree_filters}
|
|
||||||
onChange={(authorId: number) => {
|
onChange={(authorId: number) => {
|
||||||
treeStore.updateFilter('authorId', authorId);
|
treeStore.updateFilter('authorId', authorId);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<UiKitInputNumber
|
<UiKitInputNumber
|
||||||
title="page.team.tree.filters.commits"
|
placeholder="page.team.tree.filters.commits"
|
||||||
help="page.team.tree.filters.help"
|
help="page.team.tree.filters.help"
|
||||||
value={treeStore.minCommits}
|
value={treeStore.minCommits || ''}
|
||||||
className={style.tree_filters}
|
className={style.table_filters_item}
|
||||||
onChange={(minCommits: number) => {
|
onChange={(minCommits: number) => {
|
||||||
treeStore.updateFilter('minCommits', minCommits);
|
treeStore.updateFilter('minCommits', minCommits);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,25 +1,28 @@
|
||||||
@import 'src/styles/variables';
|
@import 'src/styles/variables';
|
||||||
|
|
||||||
.tree_filters {
|
.table_filters {
|
||||||
display: inline-block;
|
&_item {
|
||||||
margin: 0 24px 24px 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tempo_filters {
|
|
||||||
margin: 0 0 24px 0;
|
|
||||||
|
|
||||||
&_date_range {
|
|
||||||
width: 260px;
|
|
||||||
max-width: 260px;
|
|
||||||
margin: 0 var(--space-xs);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_user {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 260px;
|
width: 350px;
|
||||||
max-width: 260px;
|
margin: 0 24px 0 0;
|
||||||
margin: 0 6px;
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_item:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.table_filters {
|
||||||
|
&_item {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_item:first-child {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,13 @@ class SettingsStore implements ISettingsStore {
|
||||||
|
|
||||||
isFullTime: boolean = true;
|
isFullTime: boolean = true;
|
||||||
|
|
||||||
defaultSalary: number = 180000;
|
defaultSalary: number = 3000;
|
||||||
|
|
||||||
defaultWorkDays: number = 5;
|
defaultWorkDays: number = 5;
|
||||||
|
|
||||||
holidaysInYear: number = 118 + 22; // праздники + выходные + отпуск
|
holidaysInYear: number = 118 + 22; // праздники + выходные + отпуск
|
||||||
|
|
||||||
currency: string = 'RUB';
|
currency: string = 'USD';
|
||||||
|
|
||||||
salary: any = {};
|
salary: any = {};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Der Angestellte
|
§ sidebar.switch.person: Der Angestellte
|
||||||
§ sidebar.buttons.settings: Die Einstellungen
|
§ sidebar.buttons.settings: Die Einstellungen
|
||||||
§ sidebar.buttons.print: Drucken
|
§ sidebar.buttons.print: Drucken
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: Die ganze Zeit
|
§ sidebar.filters.all: Die ganze Zeit
|
||||||
§ sidebar.filters.year: Jahr
|
§ sidebar.filters.year: Jahr
|
||||||
§ sidebar.filters.halfYear: ein halbes Jahr
|
§ sidebar.filters.halfYear: ein halbes Jahr
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Employee
|
§ sidebar.switch.person: Employee
|
||||||
§ sidebar.buttons.settings: Settings
|
§ sidebar.buttons.settings: Settings
|
||||||
§ sidebar.buttons.print: Print
|
§ sidebar.buttons.print: Print
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: all time
|
§ sidebar.filters.all: all time
|
||||||
§ sidebar.filters.year: year
|
§ sidebar.filters.year: year
|
||||||
§ sidebar.filters.halfYear: half year
|
§ sidebar.filters.halfYear: half year
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Empleado
|
§ sidebar.switch.person: Empleado
|
||||||
§ sidebar.buttons.settings: Ajustes
|
§ sidebar.buttons.settings: Ajustes
|
||||||
§ sidebar.buttons.print: Impresión
|
§ sidebar.buttons.print: Impresión
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: a todas horas
|
§ sidebar.filters.all: a todas horas
|
||||||
§ sidebar.filters.year: año
|
§ sidebar.filters.year: año
|
||||||
§ sidebar.filters.halfYear: medio año
|
§ sidebar.filters.halfYear: medio año
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Employé
|
§ sidebar.switch.person: Employé
|
||||||
§ sidebar.buttons.settings: Réglages
|
§ sidebar.buttons.settings: Réglages
|
||||||
§ sidebar.buttons.print: Impression
|
§ sidebar.buttons.print: Impression
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: à toute heure
|
§ sidebar.filters.all: à toute heure
|
||||||
§ sidebar.filters.year: année
|
§ sidebar.filters.year: année
|
||||||
§ sidebar.filters.halfYear: demi-année
|
§ sidebar.filters.halfYear: demi-année
|
||||||
|
|
|
@ -3,12 +3,14 @@ export default `
|
||||||
§ sidebar.switch.person: 従業員
|
§ sidebar.switch.person: 従業員
|
||||||
§ sidebar.buttons.settings: 設定
|
§ sidebar.buttons.settings: 設定
|
||||||
§ sidebar.buttons.print: 印刷
|
§ sidebar.buttons.print: 印刷
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: すべての時間
|
§ sidebar.filters.all: すべての時間
|
||||||
§ sidebar.filters.year: 年
|
§ sidebar.filters.year: 年
|
||||||
§ sidebar.filters.halfYear: 半年
|
§ sidebar.filters.halfYear: 半年
|
||||||
§ sidebar.filters.month: 月
|
§ sidebar.filters.month: 月
|
||||||
§ sidebar.filters.week: 一週間
|
§ sidebar.filters.week: 週間
|
||||||
§ sidebar.team.total: 一般的な情報
|
§ sidebar.team.total: 般的な情報
|
||||||
§ sidebar.team.scope: モジュール
|
§ sidebar.team.scope: モジュール
|
||||||
§ sidebar.team.author: スタッフ
|
§ sidebar.team.author: スタッフ
|
||||||
§ sidebar.team.type: タスクの種類
|
§ sidebar.team.type: タスクの種類
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Funcionário
|
§ sidebar.switch.person: Funcionário
|
||||||
§ sidebar.buttons.settings: Sintonização
|
§ sidebar.buttons.settings: Sintonização
|
||||||
§ sidebar.buttons.print: Impressão
|
§ sidebar.buttons.print: Impressão
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: o tempo todo
|
§ sidebar.filters.all: o tempo todo
|
||||||
§ sidebar.filters.year: ano
|
§ sidebar.filters.year: ano
|
||||||
§ sidebar.filters.halfYear: meio ano
|
§ sidebar.filters.halfYear: meio ano
|
||||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
||||||
§ sidebar.switch.person: Сотрудник
|
§ sidebar.switch.person: Сотрудник
|
||||||
§ sidebar.buttons.settings: Настройки
|
§ sidebar.buttons.settings: Настройки
|
||||||
§ sidebar.buttons.print: Печать
|
§ sidebar.buttons.print: Печать
|
||||||
|
§ sidebar.buttons.share: Расшарить
|
||||||
|
§ sidebar.buttons.logout: Выйти
|
||||||
§ sidebar.filters.all: всё время
|
§ sidebar.filters.all: всё время
|
||||||
§ sidebar.filters.year: год
|
§ sidebar.filters.year: год
|
||||||
§ sidebar.filters.halfYear: пол года
|
§ sidebar.filters.halfYear: пол года
|
||||||
|
|
|
@ -3,12 +3,14 @@ export default `
|
||||||
§ sidebar.switch.person: 雇员
|
§ sidebar.switch.person: 雇员
|
||||||
§ sidebar.buttons.settings: 设置
|
§ sidebar.buttons.settings: 设置
|
||||||
§ sidebar.buttons.print: 印刷业
|
§ sidebar.buttons.print: 印刷业
|
||||||
|
§ sidebar.buttons.share: Share
|
||||||
|
§ sidebar.buttons.logout: Logout
|
||||||
§ sidebar.filters.all: 一直
|
§ sidebar.filters.all: 一直
|
||||||
§ sidebar.filters.year: 年份
|
§ sidebar.filters.year: 年份
|
||||||
§ sidebar.filters.halfYear: 半年
|
§ sidebar.filters.halfYear: 半年
|
||||||
§ sidebar.filters.month: 月份
|
§ sidebar.filters.month: 月份
|
||||||
§ sidebar.filters.week: 一周
|
§ sidebar.filters.week: 周
|
||||||
§ sidebar.team.total: 一般资料
|
§ sidebar.team.total: 般资料
|
||||||
§ sidebar.team.scope: 功能
|
§ sidebar.team.scope: 功能
|
||||||
§ sidebar.team.author: 员工
|
§ sidebar.team.author: 员工
|
||||||
§ sidebar.team.type: 任务类型
|
§ sidebar.team.type: 任务类型
|
||||||
|
@ -26,7 +28,7 @@ export default `
|
||||||
§ sidebar.team.words: 流行语
|
§ sidebar.team.words: 流行语
|
||||||
§ sidebar.team.top: 测验
|
§ sidebar.team.top: 测验
|
||||||
§ sidebar.team.settings: 设置
|
§ sidebar.team.settings: 设置
|
||||||
§ sidebar.person.total: 一般资料
|
§ sidebar.person.total: 般资料
|
||||||
§ sidebar.person.money: 工作的成本
|
§ sidebar.person.money: 工作的成本
|
||||||
§ sidebar.person.speed: 速度
|
§ sidebar.person.speed: 速度
|
||||||
§ sidebar.person.day: 白天
|
§ sidebar.person.day: 白天
|
||||||
|
|