JIRA-1234 fix(fox): cocs

This commit is contained in:
Бахирев 2023-12-20 00:26:48 +03:00
parent c7f3a4768c
commit 81a605de6e
51 changed files with 964 additions and 308 deletions

View file

@ -1,17 +1,19 @@
{
"files": {
"main.css": "./static/css/main.98731101.css",
"main.js": "./static/js/main.c257580d.js",
"main.css": "./static/css/main.372cdf70.css",
"main.js": "./static/js/main.bc524aaf.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.98731101.css.map": "./static/css/main.98731101.css.map",
"main.c257580d.js.map": "./static/js/main.c257580d.js.map"
"static/media/arrow_left.svg": "./static/media/arrow_left.d053cbdc58069cfc01de.svg",
"static/media/arrow_right.svg": "./static/media/arrow_right.7caaf9eb44d9210be019.svg",
"main.372cdf70.css.map": "./static/css/main.372cdf70.css.map",
"main.bc524aaf.js.map": "./static/js/main.bc524aaf.js.map"
},
"entrypoints": [
"static/css/main.98731101.css",
"static/js/main.c257580d.js"
"static/css/main.372cdf70.css",
"static/js/main.bc524aaf.js"
]
}

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="#84858D" d="M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"></path>
</svg>

After

Width:  |  Height:  |  Size: 198 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="#84858D" d="M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 188 B

View file

@ -1 +1 @@
<!doctype html><html lang="ru"><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 статистика</title><meta name="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta name="keywords" content="git, статистика, аудит, история, log, мониторинг, контроль сотрудников"><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 Статистика"><meta name="msapplication-tooltip" content="Простой и быстрый отчёт по истории коммитов в git."><meta property="og:title" content="GIT Статистика"><meta property="og:description" content="Простой и быстрый отчёт по истории коммитов в git."><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 Статистика"><meta name="twitter:description" content="Простой и быстрый отчёт по истории коммитов в git."><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 Статистика"><meta itemprop="description" content="Простой и быстрый отчёт по истории коммитов в git."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.c257580d.js"></script><link href="./static/css/main.98731101.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 lang="en"><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.bc524aaf.js"></script><link href="./static/css/main.372cdf70.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="#84858D" d="M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"></path>
</svg>

After

Width:  |  Height:  |  Size: 198 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke="#84858D" d="M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 188 B

View file

@ -25,7 +25,7 @@ const Header = observer(({
<img
id={`${id}-close`}
src="./assets/close.svg"
className={`${style.modal_window_close} ${className || ''}`}
className={style.modal_window_close}
onClick={(event: any) => {
event.stopPropagation();
onClose();

View file

@ -28,7 +28,7 @@ function Modal({
return ReactDOM.createPortal((
<div
id={`${id}-wrapper`}
className={`${style.modal_window_wrapper || ''} ${className || ''}`}
className={`${style.modal_window_wrapper || ''}`}
onClick={(event: any) => {
event.stopPropagation();
if (event.target?.id !== `${id}-wrapper`) return;

View file

@ -1,9 +1,11 @@
import React from 'react';
import Description from 'ts/components/Description';
import UiKitButton from 'ts/components/UiKit/components/Button';
import localization from 'ts/helpers/Localization';
import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants';
import { getFormattedTitle, getDescriptionText } from '../helpers';
import style from '../styles/card.module.scss';
function getClassName(recommendation?: any) {
@ -16,59 +18,40 @@ function getClassName(recommendation?: any) {
}[type || RECOMMENDATION_TYPES.INFO] ?? style.recommendations_card_fact;
}
function getDescriptionText(recommendation?: any) {
const descriptionArgs = recommendation?.arguments?.description;
const { description } = recommendation;
const list = Array.isArray(description)
? description
: [description];
return list.map((textId: string) => (
localization.get(textId, descriptionArgs)
)).join('\n');
}
interface IRecommendationsProps {
recommendation: any;
onClick: Function;
}
function Card({
recommendation,
onClick,
}: IRecommendationsProps) {
if (!recommendation) return null;
const { title } = recommendation;
let formattedTitle = title || '';
if (Array.isArray(title)) {
formattedTitle = title.length > 1
? `${title[0]} +${title.length - 1}`
: title[0];
}
const className = getClassName(recommendation);
const title = getFormattedTitle(recommendation);
const titleArgs = recommendation?.arguments?.title;
const parts = getDescriptionText(recommendation).split('\n');
const previewText = parts.shift();
const mainText = parts.join('\n');
return (
<div className={`${style.recommendations_card} ${className}`}>
<div className={style.recommendations_card_wrapper}>
<h5 className={style.recommendations_card_title}>
<span className={style.recommendations_card_icon}></span>
{localization.get(formattedTitle, titleArgs)}
{localization.get(title, titleArgs)}
</h5>
<Description
style={{ color: '#12131B' }}
text={previewText || ''}
/>
<div className={style.recommendations_card_shortcut}>
<Description
style={{ color: '#12131B' }}
text={mainText || ''}
/>
</div>
</div>
<UiKitButton
type="link"
className={style.recommendations_card_button}
onClick={onClick}
>
Подробнее
</UiKitButton>
</div>
);
}

View file

@ -0,0 +1,70 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import UiKitButton from 'ts/components/UiKit/components/Button';
import { Modal, Header, Body, Footer } from 'ts/components/ModalWindow';
import Description from 'ts/components/Description';
import localization from 'ts/helpers/Localization';
import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants';
import { getFormattedTitle, getDescriptionText } from '../helpers';
import recommendationStore from '../store/index';
import style from '../styles/modal.module.scss';
function getClassName(recommendation?: any) {
const type = recommendation?.type;
return {
[RECOMMENDATION_TYPES.INFO]: style.recommendations_modal_info,
[RECOMMENDATION_TYPES.FACT]: style.recommendations_modal_fact,
[RECOMMENDATION_TYPES.WARNING]: style.recommendations_modal_warning,
[RECOMMENDATION_TYPES.ALERT]: style.recommendations_modal_error,
}[type || RECOMMENDATION_TYPES.INFO] ?? style.recommendations_modal_fact;
}
const RecommendationDescription = observer(() => {
const { recommendation } = recommendationStore;
if (!recommendation) return null;
const title = getFormattedTitle(recommendation);
const titleArgs = recommendation?.arguments?.title;
const className = getClassName(recommendation);
const parts = getDescriptionText(recommendation).split('\n');
const subTitle = parts.shift();
return (
<Modal
className={`${className} ${style.recommendations_modal}`}
onClose={() => {
recommendationStore.close();
}}
>
<Header className={style.recommendations_modal_header}>
<span className={style.recommendations_modal_title}>
{localization.get(title, titleArgs)}
</span>
<p className={style.recommendations_modal_sub_title}>
{subTitle}
</p>
</Header>
<Body>
<Description
className={style.recommendations_modal_description}
text={parts}
/>
</Body>
<Footer className={style.recommendations_modal_footer}>
<UiKitButton
type="slim"
onClick={() => {
recommendationStore.close();
}}
>
{localization.get('page.print.modal.cancel')}
</UiKitButton>
</Footer>
</Modal>
);
});
export default RecommendationDescription;

View file

@ -0,0 +1,25 @@
import localization from 'ts/helpers/Localization';
export function getFormattedTitle(recommendation: any) {
const { title } = recommendation;
if (!Array.isArray(title)) {
return title || '';
}
return title.length > 1
? `${title[0]} +${title.length - 1}`
: title[0];
}
export function getDescriptionText(recommendation?: any) {
const { description } = recommendation;
const descriptionArgs = recommendation?.arguments?.description;
const list = Array.isArray(description)
? description
: [description];
return list.map((textId: string) => (
localization.get(textId, descriptionArgs)
)).join('\n');
}

View file

@ -1,6 +1,10 @@
import React, { useLayoutEffect, useRef, useState } from 'react';
import React from 'react';
import Title from 'ts/components/Title';
import localization from 'ts/helpers/Localization';
import Card from './components/Card';
import recommendationStore from './store/index';
import style from './styles/index.module.scss';
interface IRecommendationsProps {
@ -10,49 +14,30 @@ interface IRecommendationsProps {
function Recommendations({
recommendations,
}: IRecommendationsProps) {
const [maxCardsOnDisplay, setMaxCardsOnDisplay] = useState<number>(5);
const [isOpen, setOpen] = useState<boolean>(false);
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
useLayoutEffect(() => {
const width = ref?.current?.offsetWidth;
const placeForCard = (width - 30) / (220 + 24);
setMaxCardsOnDisplay(placeForCard);
}, []);
const className = isOpen
? style.recommendations_full
: style.recommendations_short;
const children = (recommendations || [])
const cards = (recommendations || [])
.filter(item => item)
.map((recommendation) => (
<Card
key={recommendation[1]}
recommendation={recommendation}
onClick={() => {
recommendationStore.open(recommendation);
}}
/>
));
const visibleChildren = children.slice(0, isOpen ? Infinity : maxCardsOnDisplay);
if (!children.length) return null;
if (!cards.length) return null;
const title = localization.get('recommendations.title');
return (
<div
ref={ref}
className={className}
>
{isOpen ? children : visibleChildren}
{!isOpen && children.length > maxCardsOnDisplay && (
<div
className={style.more}
onClick={() => {
setOpen(true);
}}
>
»
</div>
)}
<>
<Title title={title}/>
<div className={style.recommendations_container}>
{cards}
</div>
</>
);
}

View file

@ -0,0 +1,30 @@
import { makeObservable, observable, action } from 'mobx';
export interface IRecommendationStore {
open: Function;
close: Function;
}
class RecommendationStore implements IRecommendationStore {
recommendation: any | null = null;
constructor() {
makeObservable(this, {
recommendation: observable,
open: action,
close: action,
});
}
open(recommendation: any) {
this.recommendation = recommendation;
}
close() {
this.recommendation = null;
}
}
const recommendationStore = new RecommendationStore();
export default recommendationStore;

View file

@ -1,75 +1,41 @@
@import '../../../../styles/variables';
.recommendations {
&_short,
&_full {
.recommendations_card {
position: relative;
display: block;
max-height: 108px;
margin: 0 0 16px 0;
}
&_full {
max-height: none;
}
&_more,
&_card {
display: inline-block;
min-height: 100px;
max-height: 100px;
width: 220px;
margin: 0 24px var(--space-l) 0;
padding: var(--space-m) var(--space-l);
text-align: left;
vertical-align: top;
box-sizing: border-box;
white-space: normal;
border-radius: var(--border-radius-m);
background-color: white;
}
&_more {
position: absolute;
top: 0;
right: 0;
width: 30px;
margin: 0 0 8px 0;
text-align: center;
line-height: 100px;
cursor: pointer;
border: 1px solid var(--color-border);
color: #AAAAAA;
}
border-left-width: var(--space-s);
border-radius: var(--border-radius-m);
&_card {
position: relative;
width: 220px;
margin: 0 12px 16px 0;
border-left: none;
&_wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
&_title {
font-weight: bold;
font-size: var(--font-xs);
display: block;
min-height: 100px;
max-height: 100px;
padding: 16px;
width: 90%;
padding: 0;
margin: 0 0 var(--space-xs);
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius-m);
border: 1px solid var(--color-border);
border-left: 8px solid var(--color-border);
background-color: white;
text-align: left;
text-decoration: none;
vertical-align: bottom;
}
&_icon {
position: absolute;
top: 16px;
left: 16px;
top: var(--space-s);
right: var(--space-s);
display: inline-block;
width: 18px;
@ -80,6 +46,12 @@
background-size: 100% auto;
}
&_button {
position: absolute;
bottom: var(--space-xs);
right: var(--space-s);
}
&_info {
--color-temp-border: #97C2A9;
--color-temp-icon: url('/assets/recommendations/info.svg');
@ -109,58 +81,18 @@
}
&_title {
font-weight: bold;
font-size: var(--font-xs);
display: block;
padding: 2px 0 0 24px;
margin: 0 auto 4px auto;
text-align: left;
text-decoration: none;
vertical-align: bottom;
color: var(--color-temp-title);
}
&_wrapper {
background-color: var(--color-temp-bg);
border-left-color: var(--color-temp-border);
}
}
}
.recommendations_card:hover > .recommendations_card_wrapper {
z-index: 2;
width: 170%;
max-height: 450px;
box-shadow: 2px 2px 3px #999999;
overflow-y: scroll;
}
.recommendations_card_wrapper::-webkit-scrollbar {
width: 8px;
background-color: transparent;
}
.recommendations_card_wrapper::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
.recommendations_title {
color: var(--color-temp-border);
}
.recommendations_card_icon {
&_icon {
background-image: var(--color-temp-icon);
}
.recommendations_card_shortcut {
display: none;
padding: 6px 0 0 0;
margin: 6px 0 0 0;
border-top: 1px solid var(--color-temp-border);
&_button {
color: var(--color-temp-title);
}
.recommendations_card:hover .recommendations_card_shortcut {
display: block;
background-color: var(--color-temp-bg);
border-left-color: var(--color-temp-border);
}

View file

@ -1,27 +1,22 @@
@import '../../../../styles/variables';
@import 'styles/variables';
.recommendations_short,
.recommendations_full {
.recommendations_container {
position: relative;
display: block;
margin: 0 0 12px 0;
margin: 0;
padding: 0;
text-align: left;
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
&::-webkit-scrollbar {
height: 8px;
background-color: transparent;
}
.more {
display: inline-block;
width: 30px;
margin: 0 0 8px 0;
text-align: center;
vertical-align: top;
box-sizing: border-box;
white-space: normal;
cursor: pointer;
border-radius: var(--border-radius-m);
line-height: 100px;
border: 1px solid var(--color-border);
color: var(--color-black);
background-color: white;
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
}

View file

@ -0,0 +1,64 @@
@import 'styles/variables';
.recommendations_modal {
&_title {
font-weight: bold;
}
&_header {
padding-bottom: var(--space-m);
margin-bottom: var(--space-s);
border-radius: 0;
border-bottom: 1px solid var(--color-border);
}
&_footer {
padding: var(--space-s) var(--space-xxl) var(--space-l) var(--space-xxl);
}
&_sub_title {
font-weight: 100;
font-size: var(--font-s);
margin: var(--space-xs) 0 0 0;
color: var(--color-black);
}
&_description {
margin-bottom: var(--space-s);
color: var(--color-black);
}
&_info {
--color-temp-border: #97C2A9;
--color-temp-bg: #E3F8EC;
--color-temp-title: #58866B;
}
&_fact {
--color-temp-border: var(--color-11);
--color-temp-bg: #EFF7FF;
--color-temp-title: var(--color-first);
}
&_warning {
--color-temp-border: var(--color-21);
--color-temp-bg: #FFF5F2;
--color-temp-title: #E8B06D;
}
&_error {
--color-temp-border: var(--color-12);
--color-temp-bg: #FFEFEE;
--color-temp-title: #DD8B87;
}
&_header {
border-bottom-color: var(--color-temp-border);
}
border: 1px solid var(--color-border);
border-left-width: var(--space-s);
background-color: var(--color-temp-bg);
border-left-color: var(--color-temp-border);
}

View file

@ -0,0 +1,77 @@
import React from 'react';
import Description from 'ts/components/Description';
import localization from 'ts/helpers/Localization';
import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants';
import style from '../styles/card.module.scss';
function getClassName(recommendation?: any) {
const type = recommendation?.type;
return {
[RECOMMENDATION_TYPES.INFO]: style.recommendations_card_info,
[RECOMMENDATION_TYPES.FACT]: style.recommendations_card_fact,
[RECOMMENDATION_TYPES.WARNING]: style.recommendations_card_warning,
[RECOMMENDATION_TYPES.ALERT]: style.recommendations_card_error,
}[type || RECOMMENDATION_TYPES.INFO] ?? style.recommendations_card_fact;
}
function getDescriptionText(recommendation?: any) {
const descriptionArgs = recommendation?.arguments?.description;
const { description } = recommendation;
const list = Array.isArray(description)
? description
: [description];
return list.map((textId: string) => (
localization.get(textId, descriptionArgs)
)).join('\n');
}
interface IRecommendationsProps {
recommendation: any;
}
function Card({
recommendation,
}: IRecommendationsProps) {
if (!recommendation) return null;
const { title } = recommendation;
let formattedTitle = title || '';
if (Array.isArray(title)) {
formattedTitle = title.length > 1
? `${title[0]} +${title.length - 1}`
: title[0];
}
const className = getClassName(recommendation);
const titleArgs = recommendation?.arguments?.title;
const parts = getDescriptionText(recommendation).split('\n');
const previewText = parts.shift();
const mainText = parts.join('\n');
return (
<div className={`${style.recommendations_card} ${className}`}>
<div className={style.recommendations_card_wrapper}>
<h5 className={style.recommendations_card_title}>
<span className={style.recommendations_card_icon}></span>
{localization.get(formattedTitle, titleArgs)}
</h5>
<Description
style={{ color: '#12131B' }}
text={previewText || ''}
/>
<div className={style.recommendations_card_shortcut}>
<Description
style={{ color: '#12131B' }}
text={mainText || ''}
/>
</div>
</div>
</div>
);
}
export default Card;

View file

@ -0,0 +1,60 @@
import React, { useLayoutEffect, useRef, useState } from 'react';
import Card from './components/Card';
import style from './styles/index.module.scss';
interface IRecommendationsProps {
recommendations: any[];
}
function Recommendations({
recommendations,
}: IRecommendationsProps) {
const [maxCardsOnDisplay, setMaxCardsOnDisplay] = useState<number>(5);
const [isOpen, setOpen] = useState<boolean>(false);
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
useLayoutEffect(() => {
const width = ref?.current?.offsetWidth;
const placeForCard = (width - 30) / (220 + 24);
setMaxCardsOnDisplay(placeForCard);
}, []);
const className = isOpen
? style.recommendations_full
: style.recommendations_short;
const children = (recommendations || [])
.filter(item => item)
.map((recommendation) => (
<Card
key={recommendation[1]}
recommendation={recommendation}
/>
));
const visibleChildren = children.slice(0, isOpen ? Infinity : maxCardsOnDisplay);
if (!children.length) return null;
return (
<div
ref={ref}
className={className}
>
{isOpen ? children : visibleChildren}
{!isOpen && children.length > maxCardsOnDisplay && (
<div
className={style.more}
onClick={() => {
setOpen(true);
}}
>
»
</div>
)}
</div>
);
}
export default Recommendations;

View file

@ -0,0 +1,166 @@
@import '../../../../styles/variables';
.recommendations {
&_short,
&_full {
position: relative;
display: block;
max-height: 108px;
margin: 0 0 16px 0;
}
&_full {
max-height: none;
}
&_more,
&_card {
display: inline-block;
min-height: 100px;
max-height: 100px;
text-align: left;
vertical-align: top;
box-sizing: border-box;
white-space: normal;
border-radius: var(--border-radius-m);
background-color: white;
}
&_more {
position: absolute;
top: 0;
right: 0;
width: 30px;
margin: 0 0 8px 0;
text-align: center;
line-height: 100px;
cursor: pointer;
border: 1px solid var(--color-border);
color: #AAAAAA;
}
&_card {
position: relative;
width: 220px;
margin: 0 12px 16px 0;
border-left: none;
&_wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
display: block;
min-height: 100px;
max-height: 100px;
padding: 16px;
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius-m);
border: 1px solid var(--color-border);
border-left: 8px solid var(--color-border);
background-color: white;
}
&_icon {
position: absolute;
top: 16px;
left: 16px;
display: inline-block;
width: 18px;
height: 18px;
background-repeat: no-repeat;
background-position: center center;
background-size: 100% auto;
}
&_info {
--color-temp-border: #97C2A9;
--color-temp-icon: url('/assets/recommendations/info.svg');
--color-temp-bg: #E3F8EC;
--color-temp-title: #58866B;
}
&_fact {
--color-temp-border: var(--color-11);
--color-temp-icon: url('/assets/recommendations/info.svg');
--color-temp-bg: #EFF7FF;
--color-temp-title: var(--color-first);
}
&_warning {
--color-temp-border: var(--color-21);
--color-temp-icon: url('/assets/recommendations/warning.svg');
--color-temp-bg: #FFF5F2;
--color-temp-title: #E8B06D;
}
&_error {
--color-temp-border: var(--color-12);
--color-temp-icon: url('/assets/recommendations/alert.svg');
--color-temp-bg: #FFEFEE;
--color-temp-title: #DD8B87;
}
&_title {
font-weight: bold;
font-size: var(--font-xs);
display: block;
padding: 2px 0 0 24px;
margin: 0 auto 4px auto;
text-align: left;
text-decoration: none;
vertical-align: bottom;
color: var(--color-temp-title);
}
&_wrapper {
background-color: var(--color-temp-bg);
border-left-color: var(--color-temp-border);
}
}
}
.recommendations_card:hover > .recommendations_card_wrapper {
z-index: 2;
width: 170%;
max-height: 450px;
box-shadow: 2px 2px 3px #999999;
overflow-y: scroll;
}
.recommendations_card_wrapper::-webkit-scrollbar {
width: 8px;
background-color: transparent;
}
.recommendations_card_wrapper::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
.recommendations_title {
color: var(--color-temp-border);
}
.recommendations_card_icon {
background-image: var(--color-temp-icon);
}
.recommendations_card_shortcut {
display: none;
padding: 6px 0 0 0;
margin: 6px 0 0 0;
border-top: 1px solid var(--color-temp-border);
}
.recommendations_card:hover .recommendations_card_shortcut {
display: block;
}

View file

@ -0,0 +1,27 @@
@import '../../../../styles/variables';
.recommendations_short,
.recommendations_full {
position: relative;
display: block;
margin: 0 0 12px 0;
}
.more {
display: inline-block;
width: 30px;
margin: 0 0 8px 0;
text-align: center;
vertical-align: top;
box-sizing: border-box;
white-space: normal;
cursor: pointer;
border-radius: var(--border-radius-m);
line-height: 100px;
border: 1px solid var(--color-border);
color: var(--color-black);
background-color: white;
}

View file

@ -5,6 +5,7 @@ import style from '../styles/index.module.scss';
export function getCustomClassName(type?: string, disabled?: boolean) {
let customClassName = {
link: style.ui_kit_button_link,
slim: style.ui_kit_button_slim,
second: style.ui_kit_button_second,
primary: style.ui_kit_button_primary,
@ -17,8 +18,8 @@ export function getCustomClassName(type?: string, disabled?: boolean) {
}
interface IUiKitButtonProps extends IUiKitWrapperProps {
type?: string,
onClick: Function,
type?: 'primary' | 'second' | 'link' | 'slim',
onClick?: Function,
}
function UiKitButton({

View file

@ -81,6 +81,14 @@
--button-color-hover: var(--color-border);
--button-color-active: var(--color-border);
}
&_link {
--button-color-bg: transparent;
--button-color-text: var(--color-button);
--button-color-border: transparent;
--button-color-hover: transparent;
--button-color-active: transparent;
}
}
.ui_kit_button {
@ -120,6 +128,18 @@
&_slim {
font-weight: 100;
}
&_link {
font-weight: 100;
height: auto;
min-height: auto;
padding: 0;
line-height: 1.3;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
.ui_kit_button + .ui_kit_button {

View file

@ -0,0 +1,6 @@
// @ts-ignore
const userAgent: string = navigator.userAgent || navigator.vendor || window.opera || '';
const isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(userAgent)
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4));
export default isMobile;

View file

@ -4,7 +4,7 @@ import dataGripStore from 'ts/store/DataGrip';
import { getDate, getDateByTimestamp } from 'ts/helpers/formatter';
import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import NothingFound from 'ts/components/NothingFound';
import PageWrapper from 'ts/components/Page/wrapper';
import BarChart from 'ts/components/BarChart';
@ -42,7 +42,7 @@ function Changes({ statistic }: IChangesProps) {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
<Title title="Количество изменённых строк по дням"/>
<PageWrapper template="box">
<BarChart

View file

@ -3,7 +3,7 @@ import React, { useState } from 'react';
import dataGripStore from 'ts/store/DataGrip';
import { getDate, getDateByTimestamp } from 'ts/helpers/formatter';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import NothingFound from 'ts/components/NothingFound';
import PageWrapper from 'ts/components/Page/wrapper';
import BarChart from 'ts/components/BarChart';
@ -45,7 +45,7 @@ function Commits({ statistic }: ICommitsProps) {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
<Title title="page.common.commits.title"/>
<PageWrapper template="box">
<BarChart

View file

@ -1,6 +1,6 @@
import React from 'react';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import NothingFound from 'ts/components/NothingFound';
import PageWrapper from 'ts/components/Page/wrapper';
import CandyChart from 'ts/components/CandyChart';
@ -37,7 +37,7 @@ function PopularWords({ statistic, mode }: IPopularWordsProps) {
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
<Title title="page.common.words.title"/>
<PageWrapper template="table">

View file

@ -2,7 +2,6 @@ import React from 'react';
import localization from 'ts/helpers/Localization';
import Description from 'ts/components/Description';
function getFlatRecommendations(translations: any, list: any[] = []) {
if (!translations) return list;

View file

@ -1,22 +1,46 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import localization from 'ts/helpers/Localization';
import { TEAM, PERSON } from '../../helpers/menu';
import style from '../../styles/light_header.module.scss';
function getPagesAndIndex(type?: string, page?: string) {
const pages = (type === 'person' ? PERSON : TEAM)
.filter((item: any) => item.id);
const index = pages
.map((item: any) => item.id)
.indexOf(page);
return { pages, index };
}
function getLink(page: any, type?: string, userId?: string) {
const nextLink = page?.link || '';
return type === 'person'
? `${nextLink}${userId}`
: nextLink;
}
function LightHeader() {
const { type, page } = useParams<any>();
const navigate = useNavigate();
const { type, page, userId } = useParams<any>();
const title = type && page
? localization.get(`sidebar.${type}.${page}`)
: localization.get('sidebar.team.total');
return (
<>
<header className={style.light_header}>
<div
className={style.light_header_button}
onClick={() => {
console.log('x');
const { pages, index } = getPagesAndIndex(type, page);
if (index < 1) return;
const nextLink = getLink(pages[index - 1], type, userId);
navigate(nextLink);
}}
/>
<h2 className={style.light_header_title}>
@ -25,10 +49,16 @@ function LightHeader() {
<div
className={style.light_header_button}
onClick={() => {
console.log('x');
const { pages, index } = getPagesAndIndex(type, page);
if (index < 0
|| index === (pages.length - 1)) return;
const nextLink = getLink(pages[index + 1], type, userId);
navigate(nextLink);
}}
/>
</header>
<div className={style.light_header_gap} />
</>
);
}

View file

@ -1,4 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import style from '../../styles/footer.module.scss';
@ -13,9 +14,19 @@ function Button({
title,
icon,
}: IButtonProps) {
console.dir(id);
const navigate = useNavigate();
return (
<figure className={style.footer_button}>
<figure
className={style.footer_button}
onClick={() => {
const link = {
team: '/team/total',
person: '/person/total/0',
settings: '/team/settings',
}[id];
if (link) navigate(link);
}}
>
<div
className={style.footer_button_icon}
style={{ backgroundImage: `url(${icon})` }}

View file

@ -0,0 +1,147 @@
export const TEAM = [
{
id: 'total',
link: '/team/total',
title: 'sidebar.team.total',
icon: './assets/menu/team_common.svg',
},
{
id: 'scope',
link: '/team/scope',
title: 'sidebar.team.scope',
icon: './assets/menu/team_feat.svg',
},
{
id: 'author',
link: '/team/author',
title: 'sidebar.team.author',
icon: './assets/menu/team_work.svg',
},
{
id: 'type',
link: '/team/type',
title: 'sidebar.team.type',
icon: './assets/menu/team_type.svg',
},
{
id: 'pr',
link: '/team/pr',
title: 'sidebar.team.pr',
icon: './assets/menu/pull_request.svg',
},
{},
{
id: 'day',
link: '/team/day',
title: 'sidebar.team.day',
icon: './assets/menu/team_week.svg',
},
{
id: 'week',
link: '/team/week',
title: 'sidebar.team.week',
icon: './assets/menu/team_week.svg',
},
{
id: 'month',
link: '/team/month',
title: 'sidebar.team.month',
icon: './assets/menu/team_date_1.svg',
},
{
id: 'hours',
link: '/team/hours',
title: 'sidebar.team.hours',
icon: './assets/menu/team_date_2.svg',
},
{},
{
id: 'tree',
link: '/team/tree',
title: 'sidebar.team.tree',
icon: './assets/menu/team_files.svg',
},
{
id: 'commits',
link: '/team/commits',
title: 'sidebar.team.commits',
icon: './assets/menu/pull-request.svg',
},
{
id: 'changes',
link: '/team/changes',
title: 'sidebar.team.changes',
icon: './assets/menu/branch.svg',
},
{
id: 'words',
link: '/team/words',
title: 'sidebar.team.words',
icon: './assets/menu/team_words.svg',
},
];
export const PERSON = [
{
id: 'total',
link: '/person/total/',
title: 'sidebar.person.total',
icon: './assets/menu/team_common.svg',
},
{
id: 'money',
link: '/person/money/',
title: 'sidebar.person.money',
icon: './assets/menu/per_money.svg',
},
{
id: 'speed',
link: '/person/speed/',
title: 'sidebar.person.speed',
icon: './assets/menu/per_speed.svg',
},
{},
{
id: 'day',
link: '/person/day/',
title: 'sidebar.person.day',
icon: './assets/menu/team_week.svg',
},
{
id: 'week',
link: '/person/week/',
title: 'sidebar.person.week',
icon: './assets/menu/team_week.svg',
},
{
id: 'month',
link: '/person/month/',
title: 'sidebar.person.month',
icon: './assets/menu/team_date_1.svg',
},
{
id: 'hours',
link: '/person/hours/',
title: 'sidebar.person.hours',
icon: './assets/menu/team_date_2.svg',
},
{},
{
id: 'commits',
link: '/person/commits/',
title: 'sidebar.person.commits',
icon: './assets/menu/pull-request.svg',
},
{
id: 'changes',
link: '/person/changes/',
title: 'sidebar.person.changes',
icon: './assets/menu/branch.svg',
},
{
id: 'words',
link: '/person/words/',
title: 'sidebar.person.words',
icon: './assets/menu/team_words.svg',
},
];

View file

@ -1,5 +1,8 @@
import React, { ReactNode } from 'react';
import Recommendations from 'ts/components/Recommendations/components/ModalDescription';
import isMobile from 'ts/helpers/isMobile';
import SideBar from './components/sidebar';
import Header from './components/header';
import Footer from './components/footer';
@ -15,7 +18,6 @@ interface IPageWrapper {
function PageWrapper({
children,
}: IPageWrapper) {
const isMobile = false;
return (
<div className={style.page_wrapper}>
{!isMobile && <SideBar />}
@ -27,6 +29,7 @@ function PageWrapper({
{children}
</div>
<Print />
<Recommendations />
{isMobile && <Footer />}
</div>
);

View file

@ -1,14 +1,25 @@
@import '../../../../styles/variables';
.light_header {
grid-area: header;
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
height: 72px;
padding: 0;
justify-content: space-between;
align-items: center;
white-space: nowrap;
box-shadow: -6px var(--space-xs) var(--space-s) #EEEEEE;
background-color: #FFFFFF;
&_gap {
grid-area: header;
height: 72px;
}
&_title {
font-size: 24px;
font-weight: 100;

View file

@ -16,7 +16,7 @@ import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
@ -131,7 +131,7 @@ const Week = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
<DataLoader
to="response"

View file

@ -21,7 +21,7 @@ import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import { getMax, getMaxByLength } from 'ts/pages/Common/helpers/getMax';
import Description from 'ts/components/Description';
@ -163,7 +163,7 @@ const Author = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
<Title title="page.team.author.title"/>
<DataLoader

View file

@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite';
import dataGripStore from 'ts/store/DataGrip';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import HoursChart from 'ts/components/HoursChart';
import Title from 'ts/components/Title';
@ -15,7 +15,7 @@ const Hours = observer((): React.ReactElement => {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
<Title title="page.team.hours.title"/>
<PageWrapper template="table">
<HoursChart statistic={statistic} />

View file

@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite';
import dataGripStore from 'ts/store/DataGrip';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import YearChart from 'ts/components/YearChart';
import Title from 'ts/components/Title';
@ -21,7 +21,7 @@ const Month = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations}/>
<Recommendations recommendations={recommendations}/>
)}
<Title title="page.team.month.title"/>
<PageWrapper template="table">

View file

@ -17,7 +17,7 @@ import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
interface IScopeViewProps {
response?: IPagination<any>;
@ -123,7 +123,7 @@ const Scope = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
<Title title="page.team.scope.title"/>
<DataLoader

View file

@ -18,7 +18,7 @@ import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import Description from 'ts/components/Description';
import { getMax } from 'ts/pages/Common/helpers/getMax';
@ -123,7 +123,7 @@ const Type = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
<Title title="page.team.type.title"/>
<DataLoader

View file

@ -18,7 +18,7 @@ import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import Recommendations from 'ts/components/Recommendations';
import { getMax } from 'ts/pages/Common/helpers/getMax';
@ -162,7 +162,7 @@ const Week = observer(({
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
<Recommendations recommendations={recommendations} />
)}
{mode === 'print' && (
<Title title="page.team.week.title"/>