This commit is contained in:
bakhirev 2024-08-10 01:08:00 +03:00
parent d0952ff866
commit 73b7b611a4
25 changed files with 80 additions and 296 deletions

View file

@ -5,11 +5,11 @@ import { useTranslation } from 'react-i18next';
import ISort from 'ts/interfaces/Sort';
import Table from 'ts/components/Table';
import Cards from 'ts/components/Cards';
import viewSettings from 'ts/store/ViewSettings';
import { downloadExcel } from 'ts/helpers/File';
import isMobile from 'ts/helpers/isMobile';
import fullScreen from 'ts/store/FullScreen';
import dataViewStore from './store';
import style from './index.module.scss';
import PageWrapper from '../Page/wrapper';
@ -41,7 +41,7 @@ function DataView({
}: IDataViewProps): React.ReactElement | null {
const { t } = useTranslation();
const urlParams = useParams<any>();
const defaultType = viewSettings.getItem(urlParams, isMobile ? 'cards' : 'table');
const defaultType = dataViewStore.getItem(urlParams, isMobile ? 'cards' : 'table');
const [localType, setType] = useState<string>(type || defaultType);
if (!rows || !rows.length) return null;
@ -89,7 +89,7 @@ function DataView({
onClick={() => {
const newType = localType === 'table' ? 'cards' : 'table';
setType(newType);
viewSettings.setItem(urlParams, newType, 'table');
dataViewStore.setItem(urlParams, newType, 'table');
}}
/>
)}

View file

@ -1,5 +1,8 @@
import React from 'react';
import CustomSelect from 'ts/components/CustomSelect';
import isMobile from 'ts/helpers/isMobile';
import { IUiKitWrapperProps } from './Wrapper';
import UiKitButton from './Button';
import UiKitSelect from './Select';
@ -41,11 +44,19 @@ function UiKitSelectWithButtons({
>
«
</UiKitButton>
<UiKitSelect
value={value}
options={options}
onChange={onChange}
/>
{isMobile ? (
<UiKitSelect
value={value}
options={options}
onChange={onChange}
/>
) : (
<CustomSelect
value={value}
options={options}
onChange={onChange}
/>
)}
<UiKitButton
mode="second"
className={style.ui_kit_select_with_buttons_right}

View file

@ -1,6 +1,6 @@
import IHashMap from 'ts/interfaces/HashMap';
import { get2Number } from 'ts/helpers/formatter';
import settingsStore from 'ts/store/Settings';
import userSettings from 'ts/store/UserSettings';
import IMonth from '../interfaces/Month';
import IWorkDay from '../interfaces/WorkDay';
@ -66,7 +66,7 @@ function addCommitsInMonth(
const uniqueAuthors = Array.from(new Set(authors));
// @ts-ignore
monthsByDate[key].money = uniqueAuthors.reduce((money: number, name: string) => {
return money + settingsStore.getMiddleSalaryInMonth(name);
return money + userSettings.getCurrentSalaryInMonth(name); // TODO: need middle salary in month
}, 0);
}
}
@ -95,4 +95,4 @@ export default function getCommitsByMonth(
addCommitsInMonth(prev, monthsByDate, commits);
return months;
}
}

View file

@ -4,7 +4,6 @@ import IHashMap from 'ts/interfaces/HashMap';
import { ONE_DAY } from 'ts/helpers/formatter';
import { increment } from 'ts/helpers/Math';
import settingsStore from 'ts/store/Settings';
import userSettings from 'ts/store/UserSettings';
export default class DataGripByAuthor {
@ -138,10 +137,9 @@ export default class DataGripByAuthor {
return total;
}
updateTotalInfo() {
updateTotalInfo(lastCommit: ICommit) {
const HOLIDAYS = 118 + 22; // праздники + выходные + отпуск
const WORK_AND_HOLIDAYS = (HOLIDAYS / (365 - HOLIDAYS));
const lastCommit = settingsStore.commits[settingsStore.commits.length - 1];
const dismissedLimit = lastCommit?.milliseconds - 32 * ONE_DAY;
this.employment = {

View file

@ -1,6 +1,6 @@
import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap';
import settingsStore from 'ts/store/Settings';
import userSettings from 'ts/store/UserSettings';
import { increment } from 'ts/helpers/Math';
interface IStatByAuthor {
@ -85,7 +85,8 @@ export default class DataGripByScope {
for (let name in dot.authors) {
const user = dot.authors[name];
const days: number = Object.keys(user.days).length;
salaryCache[name] = salaryCache[name] || settingsStore.getMiddleSalaryInDay(name);
// TODO: need middle salary in month;
salaryCache[name] = salaryCache[name] || userSettings.getCurrentSalaryInMonth(name);
cost += days * salaryCache[name];
dot.authors[name] = { ...user, days };
}

View file

@ -1,6 +1,6 @@
import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap';
import settingsStore from 'ts/store/Settings';
import userSettings from 'ts/store/UserSettings';
import { increment } from 'ts/helpers/Math';
import MinMaxCounter from './counter';
@ -119,7 +119,7 @@ export default class DataGripByTimestamp {
#getWeekendPaymentByAuthor(statistic: any, dataGripByAuthor: any) {
if (dataGripByAuthor.isStaff) return 0;
const salaryInDay = settingsStore.getMiddleSalaryInDay(dataGripByAuthor.author);
const salaryInDay = userSettings.getCurrentSalaryInMonth(dataGripByAuthor.author); // TODO: need middle salary in month
const saturday = statistic.workByDay[5] * salaryInDay;
const sunday = statistic.workByDay[6] * salaryInDay;
return saturday + sunday;

View file

@ -1,6 +1,5 @@
import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap';
import settingsStore from 'ts/store/Settings';
import { increment } from 'ts/helpers/Math';
export default class DataGripByWeek {
@ -79,8 +78,8 @@ export default class DataGripByWeek {
authorsLength += 1;
workDays[name] = Object.keys(dot.workDays[name]).length;
workDaysTotal += workDays[name];
const limit = settingsStore.workDays[name] || settingsStore.defaultWorkDays;
// userSettings.getCurrentWorkDaysInWeek(name); TODO: need middle salary in month
const limit = 5;
const lazyDaysValue = limit - workDays[name];
const weekDaysValue = workDays[name] - limit;

View file

@ -1,6 +1,5 @@
import ICommit, { ISystemCommit } from 'ts/interfaces/Commit';
import settingsStore from 'ts/store/Settings';
import Recommendations from 'ts/helpers/Recommendations';
import DataGripByAuthor from './components/author';
@ -40,10 +39,6 @@ class DataGrip {
release: any = new DataGripByRelease();
initializationInfo: any = {};
hash: number = 0;
clear() {
this.firstLastCommit.clear();
this.author.clear();
@ -75,8 +70,8 @@ class DataGrip {
}
}
#updateTotalInfo() {
this.author.updateTotalInfo();
updateTotalInfo() {
this.author.updateTotalInfo(this.firstLastCommit.maxData);
this.team.updateTotalInfo(this.author);
this.scope.updateTotalInfo();
this.type.updateTotalInfo();
@ -87,28 +82,6 @@ class DataGrip {
this.tasks.updateTotalInfo(this.pr);
this.release.updateTotalInfo();
}
updateByInitialization() {
this.#updateTotalInfo();
this.initializationInfo = this.author.statistic
.reduce((info: any, author: any) => {
info[author.author] = { ...author };
return info;
}, {});
}
updateByFilters() {
this.clear();
settingsStore.commits.forEach((commit: ICommit) => {
const author = this.initializationInfo[commit.author] || { commits: 0 };
if (commit.timestamp < settingsStore.from
|| commit.timestamp > settingsStore.to
|| author.commits < settingsStore.minCommits) return;
this.addCommit(commit);
});
this.#updateTotalInfo();
this.hash = Math.random();
}
}
const dataGrip = new DataGrip();

View file

@ -47,10 +47,6 @@ export function get2Number(time: number) {
return time < 10 ? `0${time}` : time;
}
export function getClearHTML(text: string) {
return (text || '').trim().replace(/(>[\s\r\n]*<)/gim, '><');
}
export function getDate(timestamp: string) {
if (!timestamp) return '';
const date = new Date(timestamp);
@ -81,7 +77,7 @@ function getCurrencyFromUSD(money: number, currency: string) {
const k = {
USD: 1,
EUR: 0.92,
RUB: 98,
RUB: 90,
CNY: 7.26,
JPY: 158,
KRW: 1360,

View file

@ -22,7 +22,7 @@ export interface ILog {
month: number; // 1,
year: number; // 2021,
timestamp: string; // 2021-02-09",
milliseconds: number; // 1612828800000,
milliseconds: number; // 1612828800000,
week: number; // 42,
// user

View file

@ -1,7 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import settingsStore from 'ts/store/Settings';
import filtersInHeaderStore from 'ts/store/FiltersInHeader';
import dataGripStore from 'ts/store/DataGrip';
import style from '../../styles/filters.module.scss';
@ -19,7 +20,8 @@ function Button({
<button
className={style.header_filters_fast_button}
onClick={() => {
settingsStore.setFilterByDateType(type);
filtersInHeaderStore.setFilterByDateType(type);
dataGripStore.updateStatistic();
}}
>
{t(title)}

View file

@ -1,7 +1,8 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import settingsStore from 'ts/store/Settings';
import filtersInHeaderStore from 'ts/store/FiltersInHeader';
import dataGripStore from 'ts/store/DataGrip';
import style from '../../styles/filters.module.scss';
@ -18,10 +19,11 @@ const Input = observer(({
<input
type="date"
placeholder={placeholder || ''}
value={settingsStore[type] ?? ''}
value={filtersInHeaderStore[type] ?? ''}
className={style.header_filters_input}
onChange={(event: any) => {
settingsStore.updateProperty(type, event.target.value);
filtersInHeaderStore.updateProperty(type, event.target.value);
dataGripStore.updateStatistic();
}}
/>
);

View file

@ -31,7 +31,7 @@ const Tasks = observer(({
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}${user.author}`}
watch={`${mode}${dataGripStore.hash}${user.author}`}
>
<TasksView
mode={mode}

View file

@ -227,7 +227,7 @@ const Author = observer(({
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<AuthorView
mode={mode}

View file

@ -135,7 +135,7 @@ const Extension = observer(({
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<ExtensionView
mode={mode}

View file

@ -127,7 +127,7 @@ const Extension = observer(({
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<ExtensionView
mode={mode}

View file

@ -127,7 +127,7 @@ const Type = observer(({
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<TypeView
mode={mode}

View file

@ -149,7 +149,7 @@ const Release = observer(({
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<ReleaseView
mode={mode}

View file

@ -135,7 +135,7 @@ const Scope = observer(({
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<ScopeView
mode={mode}

View file

@ -147,7 +147,7 @@ const Tasks = observer(({
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<br/>
<br/>

View file

@ -139,7 +139,7 @@ const Type = observer(({
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<TypeView
mode={mode}

View file

@ -183,7 +183,7 @@ const Week = observer(({
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort,
})}
watch={`${mode}${dataGripStore.dataGrip.hash}`}
watch={`${mode}${dataGripStore.hash}`}
>
<WeekView
mode={mode}

View file

@ -11,7 +11,7 @@ import getTitle from 'ts/helpers/Title';
import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings';
import { applicationHasCustom } from 'ts/helpers/RPC';
import settingsStore from './Settings';
import filtersInHeaderStore from './FiltersInHeader';
export enum DataParseStatusEnum {
WAITING = 'waiting',
@ -34,14 +34,18 @@ class DataGripStore implements IDataGripStore {
fileGrip: any = null;
hash: number = 0;
status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING;
constructor() {
makeObservable(this, {
commits: observable,
dataGrip: observable,
hash: observable,
status: observable,
setCommits: action,
updateStatistic: action,
});
}
@ -65,20 +69,17 @@ class DataGripStore implements IDataGripStore {
: DataParseStatusEnum.WAITING;
if (this.status === DataParseStatusEnum.DONE) {
setDefaultValues(dataGrip.firstLastCommit.minData, dataGrip.firstLastCommit.maxData);
settingsStore.updateByCommits(
this.commits,
filtersInHeaderStore.updateByCommits(
dataGrip.firstLastCommit.minData,
dataGrip.firstLastCommit.maxData,
);
setDefaultValues(dataGrip.firstLastCommit.minData, dataGrip.firstLastCommit.maxData);
dataGrip.updateByInitialization();
dataGrip.updateTotalInfo();
achievements.updateByGrip(dataGrip, fileGrip);
}
this.dataGrip = null;
this.dataGrip = dataGrip;
this.fileGrip = fileGrip;
this.#updateRender();
console.dir(this.dataGrip);
console.dir(this.fileGrip);
@ -87,13 +88,27 @@ class DataGripStore implements IDataGripStore {
}
}
updateChars() { // todo: remove, never use
console.log('need update data TODO');
dataGrip.updateByFilters();
if (!dataGrip.author.list.length) return;
updateStatistic() {
dataGrip.clear();
fileGrip.clear();
this.commits.forEach((commit: ICommit | ISystemCommit) => {
if (commit.timestamp < filtersInHeaderStore.from
|| commit.timestamp > filtersInHeaderStore.to) return;
dataGrip.addCommit(commit);
fileGrip.addCommit(commit);
});
fileGrip.updateTotalInfo();
dataGrip.updateTotalInfo();
achievements.updateByGrip(dataGrip, fileGrip);
this.#updateRender();
}
#updateRender() {
this.dataGrip = null;
this.dataGrip = dataGrip;
this.fileGrip = null;
this.fileGrip = fileGrip;
this.hash = Math.random();
}
}

View file

@ -1,144 +0,0 @@
import { makeObservable, observable, action } from 'mobx';
import { ONE_DAY } from 'ts/helpers/formatter';
import ICommit from '../interfaces/Commit';
import dataGripStore from './DataGrip';
interface ISettingsStore {
commits: ICommit[];
defaultFrom: string;
defaultTo: string;
TODAY: Date;
from: string;
to: string;
minCommits: number;
isFullTime: boolean;
defaultSalary: number;
defaultWorkDays: number;
holidaysInYear: number;
currency: string;
salary: any;
workDays: any;
updateByCommits: (commits: ICommit[], first: ICommit, last: ICommit) => void,
setFilterByDateType: (type: string) => void,
}
class SettingsStore implements ISettingsStore {
commits: ICommit[] = [];
defaultFrom: string = '';
defaultTo: string = '';
TODAY: Date = new Date();
from: string = '';
to: string = '';
minCommits: number = 20;
isFullTime: boolean = true;
defaultSalary: number = 3000;
defaultWorkDays: number = 5;
holidaysInYear: number = 118 + 22; // праздники + выходные + отпуск
currency: string = 'USD';
salary: any = {};
workDays: any = {};
constructor() {
makeObservable(this, {
commits: observable,
defaultFrom: observable,
defaultTo: observable,
TODAY: observable,
from: observable,
to: observable,
minCommits: observable,
isFullTime: observable,
defaultSalary: observable,
defaultWorkDays: observable,
holidaysInYear: observable,
currency: observable,
salary: observable,
workDays: observable,
updateByCommits: action,
setFilterByDateType: action,
updateProperty: action,
setSalary: action,
});
}
getMiddleSalaryInMonth(name: string): number {
return this.salary[name] || this.defaultSalary;
}
getMiddleSalaryInDay(name: string) {
const salaryInMonth = this.getMiddleSalaryInMonth(name);
const workDaysInWeek = this.workDays[name] || this.defaultWorkDays;
const workDaysInMonth = Math.ceil(4.3 * workDaysInWeek);
return salaryInMonth / workDaysInMonth;
}
getValue(property: string) {
return property.split('.').reduce((acc, key) => acc[key], this);
}
updateByCommits(commits: ICommit[], firstCommit: ICommit, lastCommit: ICommit) {
this.commits = commits;
this.defaultFrom = firstCommit.timestamp;
this.defaultTo = lastCommit.timestamp;
this.TODAY = new Date(this.defaultTo);
this.from = this.defaultFrom;
this.to = this.defaultTo;
this.minCommits = 20;
}
setFilterByDateType(type: string) {
const count = {
year: 365,
halfYear: 183,
month: 30,
week: 7,
day: 1,
}[type];
this.from = count
? (new Date(this.TODAY.getTime() - ONE_DAY * count)).toISOString().split('T')[0]
: this.defaultFrom;
this.to = this.defaultTo;
this.minCommits = {
all: 20,
year: 20,
halfYear: 10,
month: 2,
}[type] || 1;
dataGripStore.updateChars();
}
updateProperty(propertyName: string, value?: any) {
this[propertyName] = value ?? null;
dataGripStore.updateChars();
}
setSalary(userName: string, salary?: number) {
this.salary[userName] = salary || this.defaultSalary;
}
}
const settingsStore = new SettingsStore();
export default settingsStore;

View file

@ -1,69 +0,0 @@
import { observable, action, makeObservable } from 'mobx';
class ViewSettingsStore {
key: string = 'view_settings';
version: number = 1;
settings: any = {};
constructor() {
this.load();
makeObservable(this, {
settings: observable,
load: action,
setItem: action,
});
}
load() {
const settings = JSON.parse(localStorage.getItem(this.key) || '{}') || {};
if (settings.version === this.version) {
this.settings = settings.settings;
}
}
save() {
if (Object.keys(this.settings).length === 0) {
localStorage.removeItem(this.key);
return;
}
localStorage.setItem(this.key, JSON.stringify({
version: this.version,
settings: this.settings,
}));
}
#getPath(path: any): string {
if (!path) return '';
if (Array.isArray(path)) {
return path.join('.');
} else if (typeof path === 'object') {
return [path.type, path.page].join('.');
}
return path;
}
setItem(path: any, value: any, defaultValue?: any) {
const formattedPath = this.#getPath(path);
if (!formattedPath) return;
if (!value || value === defaultValue) {
delete this.settings[formattedPath];
} else {
this.settings[formattedPath] = value;
}
this.save();
}
getItem(path: any, defaultValue?: any): any {
const formattedPath = this.#getPath(path);
return this.settings?.[formattedPath] || defaultValue;
}
}
const viewSettings = new ViewSettingsStore();
export default viewSettings;