diff --git a/src/ts/components/CardWithIcon/components/Banner.tsx b/src/ts/components/CardWithIcon/components/Banner.tsx index 6646c88..7ca92e4 100644 --- a/src/ts/components/CardWithIcon/components/Banner.tsx +++ b/src/ts/components/CardWithIcon/components/Banner.tsx @@ -5,23 +5,24 @@ import Banner from 'ts/components/Banner'; import style from '../index.module.scss'; interface ICardWithBannerProps { - long?: boolean; + size?: 's' | 'm' | 'l'; } -function CardWithBanner({ - long = false, -}: ICardWithBannerProps): React.ReactElement | null { - const className = long - ? style.card_with_icon_long - : style.card_with_icon; +function CardWithBanner({ size }: ICardWithBannerProps): React.ReactElement | null { + const className = [ + style.card_with_icon, + style.card_with_icon_banner, + ]; + if (size === 's') className.push(style.card_with_icon_small); + if (size === 'l') className.push(style.card_with_icon_long); return ( - + ); } CardWithBanner.defaultProps = { - long: false, + size: 'm', }; export default CardWithBanner; diff --git a/src/ts/components/CardWithIcon/components/Scoring.tsx b/src/ts/components/CardWithIcon/components/Scoring.tsx index bb0fecd..f3d1a4b 100644 --- a/src/ts/components/CardWithIcon/components/Scoring.tsx +++ b/src/ts/components/CardWithIcon/components/Scoring.tsx @@ -15,6 +15,7 @@ function Scoring({ total, }: IScoringProps): React.ReactElement | null { const { t } = useTranslation(); + if (!value) return null; return ( diff --git a/src/ts/components/CardWithIcon/index.module.scss b/src/ts/components/CardWithIcon/index.module.scss index a77a6ab..e3fe038 100644 --- a/src/ts/components/CardWithIcon/index.module.scss +++ b/src/ts/components/CardWithIcon/index.module.scss @@ -1,14 +1,13 @@ @import 'src/styles/variables'; -.card_with_icon, -.card_with_icon_long { +.card_with_icon { position: relative; display: inline-block; width: calc(50% - 12px); min-height: 270px; - margin: 0 24px 24px 0; - padding: 16px; + margin: 0 var(--space-xxl) var(--space-xxl) 0; + padding: var(--space-l); vertical-align: top; text-decoration: none; @@ -35,10 +34,13 @@ &_value { font-weight: bold; font-size: 28px; + display: block; - margin: 0 0 4px 0; + margin: 0 0 var(--space-xxs) 0; padding: 0; + text-align: center; + color: var(--color-11); } @@ -46,19 +48,22 @@ &_description, &_scoring { font-weight: 100; + display: block; margin: 0 auto; padding: 0; + line-height: 1.3; text-align: center; text-decoration: none; + color: var(--color-black); } &_title { font-size: var(--font-m); line-height: 14px; - margin: 0 0 4px 0; + margin: 0 0 var(--space-xxs) 0; } &_description, @@ -68,12 +73,6 @@ color: var(--color-grey); } - &_banner { - font-size: 32px; - padding: 0; - line-height: 270px; - } - &_scoring { position: absolute; bottom: -11px; @@ -82,6 +81,7 @@ display: inline-block; padding: var(--space-xxxs) var(--space-xs); + white-space: nowrap; text-align: center; @@ -89,11 +89,31 @@ border: 1px solid var(--color-border); background-color: var(--color-white); } + + &_small { + min-height: auto; + padding: var(--space-l) 0; + } + + &_long { + width: 100%; + margin: 0 0 var(--space-xxl) 0; + } + + &_banner { + font-size: 32px; + padding: 0; + line-height: 270px; + } } -.card_with_icon_long { - width: 100%; - margin: 0 0 24px 0; +.card_with_icon_small { + .card_with_icon_value { + font-size: var(--font-m); + font-weight: 100; + margin-bottom: var(--space-m); + color: var(--color-button-2); + } } .card_with_icon:nth-child(2n+2) { @@ -101,10 +121,9 @@ } @media (max-width: 900px) { - .card_with_icon, - .card_with_icon_long { + .card_with_icon { min-height: 220px; - padding: 16px 0; + padding: var(--space-l) 0; &_title { margin: 0; @@ -117,8 +136,7 @@ } @media (max-width: 650px) { - .card_with_icon, - .card_with_icon_long { + .card_with_icon { min-height: 190px; padding: 32px 0; @@ -139,10 +157,9 @@ } @media print { - .card_with_icon, - .card_with_icon_long { + .card_with_icon { min-height: 220px; - padding: 16px 0; + padding: var(--space-l) 0; &_icon { height: 60px; @@ -155,7 +172,7 @@ &_title { font-size: var(--font-s); - margin: 0 0 8px 0; + margin: 0 0 var(--space-s) 0; } &_description { diff --git a/src/ts/components/CardWithIcon/index.tsx b/src/ts/components/CardWithIcon/index.tsx index ee8a529..0f2bea5 100644 --- a/src/ts/components/CardWithIcon/index.tsx +++ b/src/ts/components/CardWithIcon/index.tsx @@ -10,9 +10,8 @@ interface ICardWithIconProps { description?: string; value: number | string | null; suffix?: string; - color?: string; icon?: string; - long?: boolean; + size?: 's' | 'm' | 'l'; scoring?: IScoringProps; } @@ -21,43 +20,47 @@ function CardWithIcon({ description, value, suffix, - color, icon, - long = false, + size, scoring, }: ICardWithIconProps): React.ReactElement | null { const { t } = useTranslation(); if (!value && value !== 0) return null; + const className = [style.card_with_icon]; + if (size === 's') className.push(style.card_with_icon_small); + if (size === 'l') className.push(style.card_with_icon_long); + return ( - + {icon && ( )} - + + {value} {suffix || ''} + {t(title || '')} + {t(description || '')} - + + {scoring ? ( + + ) : null} ); } @@ -65,9 +68,8 @@ function CardWithIcon({ CardWithIcon.defaultProps = { description: '', suffix: '', - color: undefined, icon: undefined, - long: false, + size: 'm', scoring: undefined, }; diff --git a/src/ts/components/SplashScreen/index.tsx b/src/ts/components/SplashScreen/index.tsx index 18f1d5d..9bd86ab 100644 --- a/src/ts/components/SplashScreen/index.tsx +++ b/src/ts/components/SplashScreen/index.tsx @@ -1,16 +1,21 @@ import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; import Logo from 'ts/pages/PageWrapper/components/sidebar/Logo'; -import globalScroll from 'ts/helpers/globalScroll'; +import splashScreenStore from './store'; import style from './index.module.scss'; import progress from './progress.module.scss'; -function SplashScreen(): React.ReactElement | null { - +const SplashScreen = observer((): React.ReactElement | null => { useEffect(() => { - globalScroll.off(5400); - }, []); + if (!splashScreenStore.isOpen) return; + setTimeout(() => { + splashScreenStore.hide(); + }, 5400); + }, [splashScreenStore.isOpen]); + + if (!splashScreenStore.isOpen) return null; return ( @@ -20,6 +25,6 @@ function SplashScreen(): React.ReactElement | null { ); -} +}); export default SplashScreen; diff --git a/src/ts/components/SplashScreen/store/index.ts b/src/ts/components/SplashScreen/store/index.ts new file mode 100644 index 0000000..0d402fe --- /dev/null +++ b/src/ts/components/SplashScreen/store/index.ts @@ -0,0 +1,27 @@ +import { observable, action, makeObservable } from 'mobx'; +import globalScroll from 'ts/helpers/globalScroll'; + +class SplashScreenStore { + isOpen: boolean = false; + + constructor() { + makeObservable(this, { + isOpen: observable, + show: action, + hide: action, + }); + } + + show() { + this.isOpen = true; + globalScroll.off(5400); + } + + hide() { + this.isOpen = false; + } +} + +const splashScreen = new SplashScreenStore(); + +export default splashScreen; diff --git a/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts index a54f218..03822d4 100644 --- a/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts +++ b/src/ts/helpers/FileGrip/components/FileBuilder/LineStat.ts @@ -26,16 +26,20 @@ export default class FileBuilderLineStat { file.removedLines += fileChange.removedLines; file.changedLines += fileChange.changedLines; - file.addedLinesByAuthor[commit.author] = file.addedLinesByAuthor[commit.author] - ? (file.addedLinesByAuthor[commit.author] + fileChange.addedLines) + // TODO: performance + const addedLinesByAuthor = file.addedLinesByAuthor[commit.author]; + file.addedLinesByAuthor[commit.author] = addedLinesByAuthor + ? (addedLinesByAuthor + fileChange.addedLines) : fileChange.addedLines; - file.removedLinesByAuthor[commit.author] = file.removedLinesByAuthor[commit.author] - ? (file.removedLinesByAuthor[commit.author] + fileChange.removedLines) + const removedLinesByAuthor = file.removedLinesByAuthor[commit.author]; + file.removedLinesByAuthor[commit.author] = removedLinesByAuthor + ? (removedLinesByAuthor + fileChange.removedLines) : fileChange.removedLines; - file.changedLinesByAuthor[commit.author] = file.changedLinesByAuthor[commit.author] - ? (file.changedLinesByAuthor[commit.author] + fileChange.changedLines) + const changedLinesByAuthor = file.changedLinesByAuthor[commit.author]; + file.changedLinesByAuthor[commit.author] = changedLinesByAuthor + ? (changedLinesByAuthor + fileChange.changedLines) : fileChange.changedLines; } diff --git a/src/ts/helpers/Parser/getCommitInfo.ts b/src/ts/helpers/Parser/getCommitInfo.ts index 8d1c973..29c9042 100644 --- a/src/ts/helpers/Parser/getCommitInfo.ts +++ b/src/ts/helpers/Parser/getCommitInfo.ts @@ -11,6 +11,8 @@ const MASTER_BRANCH = { let prevDate = new Date(); +let refTimestampTime = {}; + export default function getCommitInfo( logString: string, refEmailAuthor: IHashMap, @@ -18,7 +20,7 @@ export default function getCommitInfo( // "2021-02-09T12:59:17+03:00>Frolov Ivan>frolov@mail.ru>profile" const parts = logString.split('>'); - const sourceDate = parts.shift() || ''; + const sourceDate = parts[0] || ''; let date = new Date(sourceDate); if (isNaN(date.getDay())) { console.log(`PARSE ERROR: Date parse error for: "${logString}"`); @@ -26,11 +28,14 @@ export default function getCommitInfo( } prevDate = date; const day = date.getDay() - 1; - const timestamp = sourceDate.split('T')[0]; + const timestamp = sourceDate.substring(0, 10); // split('T')[0]; + if (!refTimestampTime[timestamp]) { + refTimestampTime[timestamp] = (new Date(timestamp)).getTime(); + } - let author = parts.shift()?.replace(/[._]/gm, ' ') || ''; - let email = parts.shift() || ''; - if (!(/@/gim).test(email)) email = ''; + let author = parts[1]?.replace(/[._]/gm, ' ') || ''; + let email = parts[2] || ''; + if (email.indexOf('@') === -1) email = ''; const authorID = author.replace(/\s|\t/gm, ''); if (authorID && refEmailAuthor[authorID] && refEmailAuthor[authorID] !== author) { @@ -52,7 +57,8 @@ export default function getCommitInfo( refEmailAuthor[author] = email; refEmailAuthor[authorID] = author; - const message = parts.join('>'); + // performance + const message = logString.substring(parts[0]?.length + parts[1]?.length + parts[2]?.length + 3); const commonInfo: any = { date: sourceDate, @@ -64,7 +70,7 @@ export default function getCommitInfo( year: date.getUTCFullYear(), week: 0, timestamp, - milliseconds: (new Date(timestamp)).getTime(), + milliseconds: refTimestampTime[timestamp], author, email, @@ -99,7 +105,7 @@ export default function getCommitInfo( task = getTask(branch); } else if (isBitbucketPR) { commitType = COMMIT_TYPE.PR_BITBUCKET; - const messageParts = message.substring(14, Infinity).split(':'); + const messageParts = message.substring(14).split(':'); prId = messageParts.shift(); task = getTask(messageParts.join(':')); } else if (isAutoMerge) { diff --git a/src/ts/helpers/Parser/getFileChanges.ts b/src/ts/helpers/Parser/getFileChanges.ts index c334d5a..1d37233 100644 --- a/src/ts/helpers/Parser/getFileChanges.ts +++ b/src/ts/helpers/Parser/getFileChanges.ts @@ -19,7 +19,7 @@ function getFilePath(path: string): string[] { return [oldPath, newPath]; } -// "38 9 src/app.css" -> [38, 9, 9, 'src/app.css'] +// "38 9 src/app.css" -> [38, 9, 'src/app.css'] export function getNumStatInfo(message: string) { let [addedRaw, removedRaw, path] = message.split('\t'); @@ -50,16 +50,17 @@ export function getNumStatInfo(message: string) { } // ":000000 100644 000000000 fc44b0a37 A public/logo192.png" -> ['A', 'public/logo192.png'] export function getRawInfo(message: string) { - const action = message[35]; - const path = message.split('\t')[1]; - return { path, action }; + return { + action:message[35], + path: message.substring(37), + }; } // "src/AppGit.css" -> { id: 'src/appgit.css', path: 'src/AppGit.css' } export function getInfoFromPath(path: string): IFileChange { const [oldPath, newPath] = getFilePath(path); - const id = oldPath.toLowerCase(); + const id = oldPath.toLowerCase(); // TODO: performance const newId = newPath?.toLowerCase(); return { diff --git a/src/ts/pages/PageWrapper/components/footer/index.tsx b/src/ts/pages/PageWrapper/components/footer/index.tsx index 46ed89e..00bf314 100644 --- a/src/ts/pages/PageWrapper/components/footer/index.tsx +++ b/src/ts/pages/PageWrapper/components/footer/index.tsx @@ -47,7 +47,7 @@ function getMenu(navigate: Function): any[] { confirm.open({ title: 'Вы уверены что хотите выйти?', }).then(() => { - dataGripStore.setCommits([]); + dataGripStore.asyncSetCommits([]); navigate('/'); }); }, diff --git a/src/ts/pages/Person/components/Money.tsx b/src/ts/pages/Person/components/Money.tsx index 070b582..8b82b80 100644 --- a/src/ts/pages/Person/components/Money.tsx +++ b/src/ts/pages/Person/components/Money.tsx @@ -82,7 +82,7 @@ const Money = observer(({ user }: IPersonCommonProps): React.ReactElement => { ) : ( { description="page.person.money.tasks.description" /> { { + {false && ( + <> + + + > + )} { total: scoringTotal.commits, }} /> - + {false && } {false && } diff --git a/src/ts/pages/Team/components/Total.tsx b/src/ts/pages/Team/components/Total.tsx index 797a075..3c25952 100644 --- a/src/ts/pages/Team/components/Total.tsx +++ b/src/ts/pages/Team/components/Total.tsx @@ -54,7 +54,7 @@ const Total = observer((): React.ReactElement => { description="page.team.total.employment.description" /> { description="page.team.total.weekendPayment.description" /> diff --git a/src/ts/pages/index.tsx b/src/ts/pages/index.tsx index 74f582e..a456086 100644 --- a/src/ts/pages/index.tsx +++ b/src/ts/pages/index.tsx @@ -1,8 +1,9 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Route, Routes } from 'react-router-dom'; import { observer } from 'mobx-react-lite'; -import dataGripStore, { DataParseStatusEnum } from 'ts/store/DataGrip'; +import dataGripStore from 'ts/store/DataGrip'; +import viewNameStore, { ViewNameEnum } from 'ts/store/ViewName'; import DropZone from 'ts/components/DropZone'; import Sponsor from 'ts/components/Sponsor'; import SplashScreen from 'ts/components/SplashScreen'; @@ -16,11 +17,7 @@ import Welcome from './Welcome/index'; import Settings from './Settings/index'; import DebugPage from './Debug/index'; -interface IViewWithChartsProps { - showSplashScreen: boolean; -} - -function ViewWithCharts({ showSplashScreen }: IViewWithChartsProps) { +function ViewWithCharts() { return ( <> @@ -75,7 +72,6 @@ function ViewWithCharts({ showSplashScreen }: IViewWithChartsProps) { )} /> - {showSplashScreen && } > ); } @@ -94,36 +90,38 @@ function ViewWithWelcome() { } const Main = observer(() => { - const [showSplashScreen, setShowSplashScreen] = useState(true); - const status = dataGripStore.status; + const view = viewNameStore.view; useEffect(() => { // @ts-ignore - dataGripStore.setCommits(window?.report || []); + const list = window?.report || []; + if (list?.length) { + dataGripStore.asyncSetCommits(list); + } else { + viewNameStore.toggle(ViewNameEnum.WELCOME); + } }, []); useEffect(() => { - if (status !== DataParseStatusEnum.DONE || window.location.hash) return; + if (view !== ViewNameEnum.INFO || window.location.hash) return; window.location.hash = '#/team/total'; - }, [status]); + }, [view]); - if (status === DataParseStatusEnum.PROCESSING) return null; + if (view === ViewNameEnum.EMPTY) return null; return ( <> - {status === DataParseStatusEnum.DONE && ( - - )} - {status === DataParseStatusEnum.WAITING && ( + {view === ViewNameEnum.WELCOME && ( )} + {view === ViewNameEnum.INFO && ( + + )} + { - setShowSplashScreen(false); - if (type === 'dump') dataGripStore.setCommits(data); - setTimeout(() => { - setShowSplashScreen(true); - }); + if (type !== 'dump') return; + dataGripStore.asyncSetCommits(data); }} /> > diff --git a/src/ts/store/DataGrip.ts b/src/ts/store/DataGrip.ts index c338917..9f664e0 100644 --- a/src/ts/store/DataGrip.ts +++ b/src/ts/store/DataGrip.ts @@ -1,4 +1,4 @@ -import { makeObservable, observable, action } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import ICommit, { ISystemCommit } from 'ts/interfaces/Commit'; @@ -9,27 +9,16 @@ import Parser from 'ts/helpers/Parser'; import getTitle from 'ts/helpers/Title'; import { setDefaultValues } from 'ts/pages/Settings/helpers/getEmptySettings'; +import splashScreenStore from 'ts/components/SplashScreen/store'; import { applicationHasCustom } from 'ts/helpers/RPC'; import Depersonalized from 'ts/helpers/Depersonalized'; import filtersInHeaderStore from './FiltersInHeader'; +import viewNameStore, { ViewNameEnum } from './ViewName'; -export enum DataParseStatusEnum { - WAITING = 'waiting', - PROCESSING = 'processing', - DONE = 'done', -} +const PROCESSING_DELAY = 300; -interface IDataGripStore { - commits: ICommit[]; - dataGrip: any; - fileGrip: any; - status: DataParseStatusEnum; - isDepersonalized: boolean; - setCommits: (log?: string[]) => void; -} - -class DataGripStore implements IDataGripStore { +class DataGripStore { commits: any[] = []; dataGrip: any = null; @@ -40,55 +29,66 @@ class DataGripStore implements IDataGripStore { isDepersonalized: boolean = false; - status: DataParseStatusEnum = DataParseStatusEnum.PROCESSING; - constructor() { makeObservable(this, { - commits: observable, dataGrip: observable, + fileGrip: observable, hash: observable, isDepersonalized: observable, - status: observable, - setCommits: action, + asyncSetCommits: action, + processingStep01: action, + processingStep03: action, depersonalized: action, updateStatistic: action, }); } - setCommits(dump?: string[]) { + asyncSetCommits(dump?: string[]) { + if (!dump?.length) return; + splashScreenStore.show(); + setTimeout(() => this.processingStep01(dump), PROCESSING_DELAY); + } + + processingStep01(dump?: string[]) { dataGrip.clear(); fileGrip.clear(); const commits = Parser(dump || []); + if (!commits.length) { + splashScreenStore.hide(); + return; + } + setTimeout(() => this.processingStep02(commits), PROCESSING_DELAY); + } + + processingStep02(commits: (ICommit | ISystemCommit)[]) { commits.sort((a, b) => a.milliseconds - b.milliseconds); commits.forEach((commit: ICommit | ISystemCommit) => { dataGrip.addCommit(commit); fileGrip.addCommit(commit); }); - fileGrip.updateTotalInfo(); + setTimeout(() => this.processingStep03(commits), PROCESSING_DELAY); + } + + processingStep03(commits: (ICommit | ISystemCommit)[]) { + fileGrip.updateTotalInfo(); this.commits = commits; - this.status = this.commits.length - ? DataParseStatusEnum.DONE - : DataParseStatusEnum.WAITING; + filtersInHeaderStore.updateByCommits( + dataGrip.firstLastCommit.minData, + dataGrip.firstLastCommit.maxData, + ); + setDefaultValues(dataGrip.firstLastCommit.minData, dataGrip.firstLastCommit.maxData); - if (this.status === DataParseStatusEnum.DONE) { - filtersInHeaderStore.updateByCommits( - dataGrip.firstLastCommit.minData, - dataGrip.firstLastCommit.maxData, - ); - setDefaultValues(dataGrip.firstLastCommit.minData, dataGrip.firstLastCommit.maxData); - - dataGrip.updateTotalInfo(); - achievements.updateByGrip(dataGrip, fileGrip); - } + dataGrip.updateTotalInfo(); + achievements.updateByGrip(dataGrip, fileGrip); + viewNameStore.toggle(ViewNameEnum.INFO); this.#updateRender(); console.dir(this.dataGrip); - console.dir(this.fileGrip); if (!applicationHasCustom.title) { document.title = getTitle(this.dataGrip, this.fileGrip, this.commits); } diff --git a/src/ts/store/ViewName.tsx b/src/ts/store/ViewName.tsx new file mode 100644 index 0000000..ae68585 --- /dev/null +++ b/src/ts/store/ViewName.tsx @@ -0,0 +1,26 @@ +import { observable, action, makeObservable } from 'mobx'; + +export enum ViewNameEnum { + EMPTY = 'empty', + WELCOME = 'welcome', + INFO = 'info', +} + +class ViewNameStore { + view: ViewNameEnum = ViewNameEnum.EMPTY; + + constructor() { + makeObservable(this, { + view: observable, + toggle: action, + }); + } + + toggle(view: ViewNameEnum) { + this.view = view; + } +} + +const viewNameStore = new ViewNameStore(); + +export default viewNameStore;
+ +
{value} {suffix || ''}