update
|
@ -1,17 +1,17 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.44209276.css",
|
||||
"main.js": "./static/js/main.5a210281.js",
|
||||
"main.css": "./static/css/main.8e65d99e.css",
|
||||
"main.js": "./static/js/main.cb604897.js",
|
||||
"static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png",
|
||||
"index.html": "./index.html",
|
||||
"static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg",
|
||||
"static/media/info.svg": "./static/media/info.954631f6b19e3fe9c495.svg",
|
||||
"static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg",
|
||||
"main.44209276.css.map": "./static/css/main.44209276.css.map",
|
||||
"main.5a210281.js.map": "./static/js/main.5a210281.js.map"
|
||||
"main.8e65d99e.css.map": "./static/css/main.8e65d99e.css.map",
|
||||
"main.cb604897.js.map": "./static/js/main.cb604897.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.44209276.css",
|
||||
"static/js/main.5a210281.js"
|
||||
"static/css/main.8e65d99e.css",
|
||||
"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 */
|
||||
|
||||
/**
|
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="address=no">
|
||||
<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">
|
||||
var report = [];
|
||||
|
|
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 localization from 'ts/helpers/Localization';
|
||||
import ru from 'ts/translations/ru/index';
|
||||
import en from 'ts/translations/en/index';
|
||||
import './ts/helpers/i18n';
|
||||
import de from 'ts/translations/de';
|
||||
import en from 'ts/translations/en';
|
||||
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 userSettings from 'ts/store/UserSettings';
|
||||
|
@ -23,8 +30,14 @@ if (module.hot) {
|
|||
module.hot.accept();
|
||||
}
|
||||
|
||||
localization.parse('de', de);
|
||||
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('zh', zh);
|
||||
|
||||
function renderReactApplication() {
|
||||
// @ts-ignore
|
||||
|
@ -45,5 +58,8 @@ function renderReactApplication() {
|
|||
}
|
||||
|
||||
userSettings.loadUserSettings().then(() => {
|
||||
applyUrlCommands(renderReactApplication);
|
||||
applyUrlCommands((parameters: any) => {
|
||||
initializationI18n(parameters.lang || parameters.language);
|
||||
renderReactApplication();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -67,6 +67,8 @@ hr {
|
|||
}
|
||||
|
||||
input, select, td {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,11 @@ function Recommendations({
|
|||
return (
|
||||
<>
|
||||
<Title title={title}/>
|
||||
<div className={className}>
|
||||
<div
|
||||
className={className}
|
||||
onTouchStart={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
{cards}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -45,6 +45,8 @@ function Table({
|
|||
<div
|
||||
ref={refTable}
|
||||
className={`${style.table_wrapper} scroll_x`}
|
||||
onTouchStart={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className={`${style.table}`}>
|
||||
<Header
|
||||
|
|
|
@ -38,6 +38,8 @@ function Tempo({
|
|||
ref={ref}
|
||||
style={customStyle}
|
||||
className={`${style.tempo_wrapper} scroll_x`}
|
||||
onTouchStart={(event) => event.stopPropagation()}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className={style.tempo}>
|
||||
{columns}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
||||
import style from '../styles/index.module.scss';
|
||||
|
@ -20,6 +21,7 @@ function UiKitInputNumber({
|
|||
placeholder,
|
||||
onChange,
|
||||
}: IUiKitSelectProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Wrapper
|
||||
title={title}
|
||||
|
@ -31,7 +33,7 @@ function UiKitInputNumber({
|
|||
<input
|
||||
type="number"
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
placeholder={placeholder ? t(placeholder) : ''}
|
||||
className={style.ui_kit_common}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChange) onChange(parseInt(event.target.value, 10) || 0);
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { ChangeEvent } from 'react';
|
|||
|
||||
import Wrapper, { IUiKitWrapperProps } from './Wrapper';
|
||||
import style from '../styles/index.module.scss';
|
||||
import styleSelect from '../styles/select.module.scss';
|
||||
|
||||
interface IUiKitSelectProps extends IUiKitWrapperProps {
|
||||
value: any;
|
||||
|
@ -46,7 +47,7 @@ function UiKitSelect({
|
|||
className={className}
|
||||
>
|
||||
<select
|
||||
className={`${style.ui_kit_common} ${style.ui_kit_select}`}
|
||||
className={`${style.ui_kit_common} ${styleSelect.ui_kit_select} ${className || ''}`}
|
||||
onChange={(event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedValue = event.target.value;
|
||||
const formattedValue = selectedValue !== 'null'
|
||||
|
|
|
@ -4,10 +4,13 @@ import { IUiKitWrapperProps } from './Wrapper';
|
|||
import UiKitButton from './Button';
|
||||
import UiKitSelect from './Select';
|
||||
|
||||
import style from '../styles/select.module.scss';
|
||||
|
||||
interface IUiKitSelectWithButtonsProps extends IUiKitWrapperProps {
|
||||
className?: string;
|
||||
value: any;
|
||||
options: any[];
|
||||
reverse: boolean;
|
||||
onChange: Function;
|
||||
}
|
||||
|
||||
|
@ -15,17 +18,25 @@ function UiKitSelectWithButtons({
|
|||
className,
|
||||
value,
|
||||
options,
|
||||
reverse,
|
||||
onChange,
|
||||
}: IUiKitSelectWithButtonsProps) {
|
||||
let index = options.map((item: any) => item.id).indexOf(value);
|
||||
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 (
|
||||
<>
|
||||
<div className={`${style.ui_kit_select_with_buttons_wrapper} ${className || ''}`}>
|
||||
<UiKitButton
|
||||
mode="second"
|
||||
disabled={index <= 0}
|
||||
className={style.ui_kit_select_with_buttons_left}
|
||||
disabled={reverse ? disabledNext : disabledPrev}
|
||||
onClick={() => {
|
||||
onChange(options[index - 1]?.id);
|
||||
onChange(reverse ? newNextValue : newPrevValue);
|
||||
}}
|
||||
>
|
||||
«
|
||||
|
@ -33,23 +44,24 @@ function UiKitSelectWithButtons({
|
|||
<UiKitSelect
|
||||
value={value}
|
||||
options={options}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<UiKitButton
|
||||
mode="second"
|
||||
disabled={index >= (options.length - 1)}
|
||||
className={style.ui_kit_select_with_buttons_right}
|
||||
disabled={reverse ? disabledPrev : disabledNext}
|
||||
onClick={() => {
|
||||
onChange(options[index + 1]?.id);
|
||||
onChange(reverse ? newPrevValue : newNextValue);
|
||||
}}
|
||||
>
|
||||
»
|
||||
</UiKitButton>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
UiKitSelectWithButtons.defaultProps = {
|
||||
reverse: false,
|
||||
className: '',
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import style from '../styles/index.module.scss';
|
||||
import style from '../styles/wrapper.module.scss';
|
||||
|
||||
export interface IUiKitWrapperProps {
|
||||
title?: string;
|
||||
|
|
|
@ -22,53 +22,6 @@
|
|||
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 {
|
||||
position: absolute;
|
||||
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;
|
||||
if (jsUrl) {
|
||||
loadJsDump(jsUrl, callback);
|
||||
loadJsDump(jsUrl, () => callback(parameters));
|
||||
} else {
|
||||
callback();
|
||||
callback(parameters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,26 @@ export function getDayPrefix(index:number) {
|
|||
][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) {
|
||||
const date = new Date(timestamp);
|
||||
const day = date.getDay() - 1;
|
||||
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),
|
||||
];
|
||||
}
|
||||
|
@ -50,7 +65,7 @@ export function getClearHTML(text: string) {
|
|||
export function getDate(timestamp: string) {
|
||||
if (!timestamp) return '';
|
||||
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) {
|
||||
|
@ -62,7 +77,7 @@ export function getDateForExcel(timestamp: string) {
|
|||
export function getShortDate(timestamp: string) {
|
||||
if (!timestamp) return '';
|
||||
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) {
|
||||
|
@ -72,7 +87,7 @@ export function getShortTime(timestamp: string) {
|
|||
}
|
||||
|
||||
export function getMoney(value: number, options?: any) {
|
||||
return (value || 0).toLocaleString('ru-RU', {
|
||||
return (value || 0).toLocaleString(getLangPrefix(), {
|
||||
style: 'currency',
|
||||
currency: settingsStore?.currency || 'USD',
|
||||
currencyDisplay: 'symbol',
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import i18next from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import ru from '../translations/ru';
|
||||
import de from '../translations/de';
|
||||
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) {
|
||||
return text
|
||||
|
@ -27,14 +33,22 @@ function getTranslationWrapper(translation: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export default function initializationI18n(defaultLanguage?: string) {
|
||||
i18next.use(initReactI18next).init({
|
||||
lng: 'ru', // if you're using a language detector, do not define the lng option
|
||||
lng: defaultLanguage || 'ru', // if you're using a language detector, do not define the lng option
|
||||
debug: false,
|
||||
resources: {
|
||||
ru: getTranslationWrapper(ru),
|
||||
de: getTranslationWrapper(de),
|
||||
en: getTranslationWrapper(en),
|
||||
es: getTranslationWrapper(es),
|
||||
fr: getTranslationWrapper(fr),
|
||||
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 { TEAM, PERSON } from '../helpers/menu';
|
||||
import PageSwiper from './Swiper';
|
||||
import style from '../styles/slider.module.scss';
|
||||
|
||||
interface ISectionSliderProps {
|
||||
|
@ -56,6 +57,11 @@ function MobileView({ getViewById }: ISectionSliderProps) {
|
|||
|
||||
if (!currentView.length) {
|
||||
return getViewById(page);
|
||||
return (
|
||||
<PageSwiper>
|
||||
{getViewById(page)}
|
||||
</PageSwiper>
|
||||
);
|
||||
}
|
||||
|
||||
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',
|
||||
title: 'sidebar.buttons.print',
|
||||
icon: './assets/menu/print.svg',
|
||||
title: 'sidebar.buttons.share',
|
||||
icon: './assets/menu/share.svg',
|
||||
onClick() {
|
||||
navigator.share({
|
||||
title: localization.get('common.title'),
|
||||
|
@ -41,8 +41,8 @@ function getMenu(navigate: Function): any[] {
|
|||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'sidebar.buttons.settings',
|
||||
icon: './assets/menu/setting.svg',
|
||||
title: 'sidebar.buttons.logout',
|
||||
icon: './assets/menu/logout.svg',
|
||||
onClick() {
|
||||
confirm.open({
|
||||
title: 'Вы уверены что хотите выйти?',
|
||||
|
|
|
@ -3,8 +3,10 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||
import { observer } from 'mobx-react-lite';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Select from 'ts/components/UiKit/components/Select';
|
||||
import Buttons from 'ts/pages/Settings/components/Buttons';
|
||||
import settingsForm from 'ts/pages/Settings/store/Form';
|
||||
import localization from 'ts/helpers/Localization';
|
||||
|
||||
import Title from './Title';
|
||||
import Filters from './Filters';
|
||||
|
@ -12,9 +14,9 @@ import printStore from '../../store/Print';
|
|||
import style from '../../styles/header.module.scss';
|
||||
|
||||
const Header = observer((): React.ReactElement | null => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<header className={style.header}>
|
||||
|
@ -24,6 +26,24 @@ const Header = observer((): React.ReactElement | null => {
|
|||
) : (
|
||||
<>
|
||||
<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
|
||||
title={t('sidebar.buttons.print')}
|
||||
className={style.header_print}
|
||||
|
|
|
@ -42,13 +42,15 @@
|
|||
border-radius: var(--border-radius-m);
|
||||
--temp-color: var(--color-grey);
|
||||
|
||||
&:hover,
|
||||
&:first-child:hover,
|
||||
&:nth-child(4n+2):hover,
|
||||
&_selected {
|
||||
background-color: #35353F;
|
||||
--temp-color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:first-child:active,
|
||||
&:nth-child(4n+2):active {
|
||||
background-color: #45454F;
|
||||
--temp-color: white;
|
||||
}
|
||||
|
|
|
@ -18,18 +18,31 @@
|
|||
}
|
||||
|
||||
&_print,
|
||||
&_lang,
|
||||
&_setting {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 4px 24px 0 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&_setting {
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
&_print {
|
||||
margin-right: 24px;
|
||||
margin: 4px 24px 0 0;
|
||||
}
|
||||
|
||||
&_lang {
|
||||
width: 50px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
margin: 0 24px 0 0;
|
||||
}
|
||||
|
||||
&_with_tab {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
display: block;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
background-color: var(--color-black);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&_main,
|
||||
|
@ -34,6 +34,7 @@
|
|||
|
||||
&_main_mobile {
|
||||
width: calc(100vw - 240px);
|
||||
padding: 24px 24px 82px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ const Month = observer(({ user }: IPersonCommonProps): React.ReactElement => {
|
|||
const max = statistic.commitsByTimestampCounter.max;
|
||||
|
||||
return (
|
||||
<>
|
||||
<br/>
|
||||
<PageWrapper template="table">
|
||||
<YearChart
|
||||
showEvents={false}
|
||||
|
@ -21,6 +23,7 @@ const Month = observer(({ user }: IPersonCommonProps): React.ReactElement => {
|
|||
wordDays={statistic.allCommitsByTimestamp}
|
||||
/>
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { IPagination } from 'ts/interfaces/Pagination';
|
||||
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 DataLoader from 'ts/components/DataLoader';
|
||||
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 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';
|
||||
|
||||
interface ITempoViewProps {
|
||||
|
@ -41,58 +36,24 @@ function getPartOfData(filters: any, rows: any[]) {
|
|||
return rows.filter((row: any) => (row.week === filters.week)).slice(0, 7);
|
||||
}
|
||||
|
||||
const Tempo = observer(({ user }: IPersonCommonProps): React.ReactElement => {
|
||||
const author = user;
|
||||
|
||||
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);
|
||||
const Tempo = observer(({ user, filters }: IPersonCommonProps): React.ReactElement => {
|
||||
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||
|
||||
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 />);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageWrapper>
|
||||
<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>
|
||||
<br/>
|
||||
<PageWrapper template="table">
|
||||
<DataLoader
|
||||
to="response"
|
||||
loader={() => getFakeLoader({ content: partOfData })}
|
||||
watch={week}
|
||||
watch={JSON.stringify(filters)}
|
||||
>
|
||||
<TempoView user={author.author}/>
|
||||
<TempoView user={user.author}/>
|
||||
<Pagination />
|
||||
</DataLoader>
|
||||
</PageWrapper>
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import dataGripStore from 'ts/store/DataGrip';
|
||||
import UiKitSelect from 'ts/components/UiKit/components/Select';
|
||||
import UiKitButton from 'ts/components/UiKit/components/Button';
|
||||
import SelectWithButtons from 'ts/components/UiKit/components/SelectWithButtons';
|
||||
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 navigate = useNavigate();
|
||||
|
||||
|
@ -16,34 +23,32 @@ const UserSelect = observer((): React.ReactElement => {
|
|||
const authors = dataGripStore.dataGrip.author.list;
|
||||
const options = authors.map((title: string, id: number) => ({ id, title }));
|
||||
|
||||
const rows = dataGripStore.dataGrip.timestamp.statistic.allCommitsByTimestamp || [];
|
||||
const weeks = useMemo(() => getFormattedWeeks(rows), [rows]);
|
||||
|
||||
return (
|
||||
<div className={style.user_select}>
|
||||
<UiKitButton
|
||||
mode="second"
|
||||
disabled={formattedUserId <= 0}
|
||||
onClick={() => {
|
||||
navigate(`/${type}/${page}/${formattedUserId - 1}`);
|
||||
}}
|
||||
>
|
||||
«
|
||||
</UiKitButton>
|
||||
<UiKitSelect
|
||||
<div className={style.table_filters}>
|
||||
<SelectWithButtons
|
||||
title="page.team.tree.filters.author"
|
||||
value={formattedUserId}
|
||||
className={style.table_filters_item}
|
||||
options={options}
|
||||
className={style.user_name}
|
||||
onChange={(newUserId: string) => {
|
||||
onChange={(newUserId: number) => {
|
||||
navigate(`/${type}/${page}/${newUserId}`);
|
||||
}}
|
||||
/>
|
||||
<UiKitButton
|
||||
mode="second"
|
||||
disabled={formattedUserId >= (authors.length - 1)}
|
||||
onClick={() => {
|
||||
navigate(`/${type}/${page}/${formattedUserId + 1}`);
|
||||
{page === 'day' ? (
|
||||
<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 });
|
||||
}}
|
||||
>
|
||||
»
|
||||
</UiKitButton>
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -132,6 +132,7 @@ const Week = observer(({
|
|||
|
||||
return (
|
||||
<>
|
||||
<br/>
|
||||
<Recommendations
|
||||
mode={mode}
|
||||
recommendations={recommendations}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -26,7 +26,7 @@ interface IPersonProps {
|
|||
userId?: string | number;
|
||||
}
|
||||
|
||||
function getViewByIdByUser(user: any) {
|
||||
function getViewByIdByUser(user: any, filters: any) {
|
||||
return function getViewById(page?: string) {
|
||||
const mode = printStore.processing ? 'print' : undefined;
|
||||
if (page === 'total') return <Total user={user}/>;
|
||||
|
@ -48,7 +48,12 @@ function getViewByIdByUser(user: any) {
|
|||
/>
|
||||
);
|
||||
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}/>;
|
||||
return <Total user={user}/>;
|
||||
};
|
||||
|
@ -59,16 +64,26 @@ const Person = observer(({
|
|||
}: IPersonProps) => {
|
||||
const { t } = useTranslation();
|
||||
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];
|
||||
if (type !== 'person' || !user) return null;
|
||||
|
||||
const getViewById = getViewByIdByUser(user);
|
||||
const getViewById = getViewByIdByUser(user, filters);
|
||||
return (
|
||||
<>
|
||||
{page !== 'print' && (
|
||||
<>
|
||||
<Title title={t('common.filters')} />
|
||||
<UserSelect />
|
||||
<UserSelect
|
||||
filters={filters}
|
||||
onChange={setFilters}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SectionSlider getViewById={getViewById} />
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export default interface IPersonCommonProps {
|
||||
user: any;
|
||||
filters?: any;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ const Common = observer((): React.ReactElement | null => {
|
|||
const [language, setLanguage] = useState<string>(localization.language);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(language);
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
|
||||
|
@ -39,6 +38,12 @@ const Common = observer((): React.ReactElement | null => {
|
|||
options={[
|
||||
{ id: 'ru', title: 'Русский' },
|
||||
{ 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) => {
|
||||
localization.language = id;
|
||||
|
|
|
@ -42,8 +42,8 @@ export default function getEmptySettings(): IUserSetting {
|
|||
return {
|
||||
version: 1,
|
||||
defaultSalary: {
|
||||
value: 180000,
|
||||
currency: 'RUB',
|
||||
value: 3000,
|
||||
currency: 'USD',
|
||||
workDaysInYear: 247,
|
||||
vacationDaysInYear: 28,
|
||||
workDaysInWeek: [1, 1, 1, 1, 1, 0, 0],
|
||||
|
|
|
@ -10,7 +10,7 @@ class Settings {
|
|||
|
||||
workDaysInMonth: number = 22;
|
||||
|
||||
salaryInDay: number = 180000 / 22;
|
||||
salaryInDay: number = 3000 / 22;
|
||||
|
||||
update(customSettings?: IUserSetting) {
|
||||
this.customSettings = customSettings || getEmptySettings();
|
||||
|
|
|
@ -3,10 +3,7 @@ import { observer } from 'mobx-react-lite';
|
|||
|
||||
import { IPagination } from 'ts/interfaces/Pagination';
|
||||
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 DataLoader from 'ts/components/DataLoader';
|
||||
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 Title from 'ts/components/Title';
|
||||
|
||||
import uiKitStyle from 'ts/components/UiKit/styles/index.module.scss';
|
||||
import style from '../styles/filters.module.scss';
|
||||
import TempoFilters from './TempoFilters';
|
||||
|
||||
interface ITempoViewProps {
|
||||
order: string[];
|
||||
|
@ -44,83 +40,38 @@ function getPartOfData(filters: any, rows: any[]) {
|
|||
|
||||
const Tempo = observer((): React.ReactElement => {
|
||||
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 firstPoint = rows[firstIndex];
|
||||
|
||||
const [week, setWeek] = useState<number>(firstPoint.week);
|
||||
const [user, setUser] = useState<string>('');
|
||||
const [filters, setFilters] = useState<any>({ week: firstPoint.week });
|
||||
const user = filters.user
|
||||
? users[filters.user - 1]
|
||||
: '';
|
||||
|
||||
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 />);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title="common.filters" />
|
||||
<PageWrapper>
|
||||
<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>
|
||||
|
||||
<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);
|
||||
}}
|
||||
<TempoFilters
|
||||
filters={filters}
|
||||
onChange={setFilters}
|
||||
/>
|
||||
<UiKitButton
|
||||
mode="second"
|
||||
onClick={() => {
|
||||
setUser(order[order.indexOf(user) + 1]);
|
||||
}}
|
||||
>
|
||||
»
|
||||
</UiKitButton>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
<br/>
|
||||
<PageWrapper template="table">
|
||||
<DataLoader
|
||||
to="response"
|
||||
loader={() => getFakeLoader({ content: partOfData })}
|
||||
watch={`${week}${user}`}
|
||||
watch={JSON.stringify(filters)}
|
||||
>
|
||||
<TempoView
|
||||
order={order}
|
||||
order={users}
|
||||
user={user}
|
||||
/>
|
||||
</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 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 treeStore from '../store/Tree';
|
||||
|
@ -16,21 +16,21 @@ const TreeFilters = observer((): React.ReactElement => {
|
|||
options.unshift({ id: 0, title: t('page.team.tree.filters.all') });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<UiKitSelect
|
||||
<div className={style.table_filters}>
|
||||
<SelectWithButtons
|
||||
title="page.team.tree.filters.author"
|
||||
value={treeStore.authorId}
|
||||
className={style.table_filters_item}
|
||||
options={options}
|
||||
className={style.tree_filters}
|
||||
onChange={(authorId: number) => {
|
||||
treeStore.updateFilter('authorId', authorId);
|
||||
}}
|
||||
/>
|
||||
<UiKitInputNumber
|
||||
title="page.team.tree.filters.commits"
|
||||
placeholder="page.team.tree.filters.commits"
|
||||
help="page.team.tree.filters.help"
|
||||
value={treeStore.minCommits}
|
||||
className={style.tree_filters}
|
||||
value={treeStore.minCommits || ''}
|
||||
className={style.table_filters_item}
|
||||
onChange={(minCommits: number) => {
|
||||
treeStore.updateFilter('minCommits', minCommits);
|
||||
}}
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
@import 'src/styles/variables';
|
||||
|
||||
.tree_filters {
|
||||
.table_filters {
|
||||
&_item {
|
||||
display: inline-block;
|
||||
margin: 0 24px 24px 0;
|
||||
width: 350px;
|
||||
margin: 0 24px 0 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;
|
||||
&_item:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&_user {
|
||||
display: inline-block;
|
||||
width: 260px;
|
||||
max-width: 260px;
|
||||
margin: 0 6px;
|
||||
@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;
|
||||
|
||||
defaultSalary: number = 180000;
|
||||
defaultSalary: number = 3000;
|
||||
|
||||
defaultWorkDays: number = 5;
|
||||
|
||||
holidaysInYear: number = 118 + 22; // праздники + выходные + отпуск
|
||||
|
||||
currency: string = 'RUB';
|
||||
currency: string = 'USD';
|
||||
|
||||
salary: any = {};
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Der Angestellte
|
||||
§ sidebar.buttons.settings: Die Einstellungen
|
||||
§ sidebar.buttons.print: Drucken
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: Die ganze Zeit
|
||||
§ sidebar.filters.year: Jahr
|
||||
§ sidebar.filters.halfYear: ein halbes Jahr
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Employee
|
||||
§ sidebar.buttons.settings: Settings
|
||||
§ sidebar.buttons.print: Print
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: all time
|
||||
§ sidebar.filters.year: year
|
||||
§ sidebar.filters.halfYear: half year
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Empleado
|
||||
§ sidebar.buttons.settings: Ajustes
|
||||
§ sidebar.buttons.print: Impresión
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: a todas horas
|
||||
§ sidebar.filters.year: año
|
||||
§ sidebar.filters.halfYear: medio año
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Employé
|
||||
§ sidebar.buttons.settings: Réglages
|
||||
§ sidebar.buttons.print: Impression
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: à toute heure
|
||||
§ sidebar.filters.year: année
|
||||
§ sidebar.filters.halfYear: demi-année
|
||||
|
|
|
@ -3,12 +3,14 @@ export default `
|
|||
§ sidebar.switch.person: 従業員
|
||||
§ sidebar.buttons.settings: 設定
|
||||
§ sidebar.buttons.print: 印刷
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: すべての時間
|
||||
§ sidebar.filters.year: 年
|
||||
§ sidebar.filters.halfYear: 半年
|
||||
§ sidebar.filters.month: 月
|
||||
§ sidebar.filters.week: 一週間
|
||||
§ sidebar.team.total: 一般的な情報
|
||||
§ sidebar.filters.week: 週間
|
||||
§ sidebar.team.total: 般的な情報
|
||||
§ sidebar.team.scope: モジュール
|
||||
§ sidebar.team.author: スタッフ
|
||||
§ sidebar.team.type: タスクの種類
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Funcionário
|
||||
§ sidebar.buttons.settings: Sintonização
|
||||
§ sidebar.buttons.print: Impressão
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: o tempo todo
|
||||
§ sidebar.filters.year: ano
|
||||
§ sidebar.filters.halfYear: meio ano
|
||||
|
|
|
@ -3,6 +3,8 @@ export default `
|
|||
§ sidebar.switch.person: Сотрудник
|
||||
§ sidebar.buttons.settings: Настройки
|
||||
§ sidebar.buttons.print: Печать
|
||||
§ sidebar.buttons.share: Расшарить
|
||||
§ sidebar.buttons.logout: Выйти
|
||||
§ sidebar.filters.all: всё время
|
||||
§ sidebar.filters.year: год
|
||||
§ sidebar.filters.halfYear: пол года
|
||||
|
|
|
@ -3,12 +3,14 @@ export default `
|
|||
§ sidebar.switch.person: 雇员
|
||||
§ sidebar.buttons.settings: 设置
|
||||
§ sidebar.buttons.print: 印刷业
|
||||
§ sidebar.buttons.share: Share
|
||||
§ sidebar.buttons.logout: Logout
|
||||
§ sidebar.filters.all: 一直
|
||||
§ sidebar.filters.year: 年份
|
||||
§ sidebar.filters.halfYear: 半年
|
||||
§ sidebar.filters.month: 月份
|
||||
§ sidebar.filters.week: 一周
|
||||
§ sidebar.team.total: 一般资料
|
||||
§ sidebar.filters.week: 周
|
||||
§ sidebar.team.total: 般资料
|
||||
§ sidebar.team.scope: 功能
|
||||
§ sidebar.team.author: 员工
|
||||
§ sidebar.team.type: 任务类型
|
||||
|
@ -26,7 +28,7 @@ export default `
|
|||
§ sidebar.team.words: 流行语
|
||||
§ sidebar.team.top: 测验
|
||||
§ sidebar.team.settings: 设置
|
||||
§ sidebar.person.total: 一般资料
|
||||
§ sidebar.person.total: 般资料
|
||||
§ sidebar.person.money: 工作的成本
|
||||
§ sidebar.person.speed: 速度
|
||||
§ sidebar.person.day: 白天
|
||||
|
|