JIRA-1234 test(doc): update some text

This commit is contained in:
bakhirev 2023-09-22 11:02:55 +03:00
parent 29e548b154
commit a8a77203a4
105 changed files with 177478 additions and 192 deletions

3
Dockerfile Normal file
View file

@ -0,0 +1,3 @@
FROM nginx
COPY build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

View file

@ -1,17 +1,17 @@
{
"files": {
"main.css": "./static/css/main.c2f3798a.css",
"main.js": "./static/js/main.0fa5ec0a.js",
"main.css": "./static/css/main.48701252.css",
"main.js": "./static/js/main.d0ec1697.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.c2f3798a.css.map": "./static/css/main.c2f3798a.css.map",
"main.0fa5ec0a.js.map": "./static/js/main.0fa5ec0a.js.map"
"main.48701252.css.map": "./static/css/main.48701252.css.map",
"main.d0ec1697.js.map": "./static/js/main.d0ec1697.js.map"
},
"entrypoints": [
"static/css/main.c2f3798a.css",
"static/js/main.0fa5ec0a.js"
"static/css/main.48701252.css",
"static/js/main.d0ec1697.js"
]
}

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M82.26,96.48v14.68h-10.63v-2.15c0-1.91,1.55-3.45,3.45-3.45h1.66v-7.97"/><path class="cls-1" d="M65.18,99.19v11.97h-10.63v-2.15c0-1.91,1.55-3.45,3.45-3.45h1.66v-11.01"/><path class="cls-1" d="M29.96,62.35c1.61,2.73,3.48,5.17,5.17,5.46,2.78,.47,8.19-4.9,10.65-7.55"/><path class="cls-1" d="M24.13,61.16c-.71-3.01-.89-5.2-.89-5.2-.22-2.61,1.86-3.25,2.87-1.58,0,0,.68,1.72,1.74,3.94"/><path class="cls-1" d="M54.24,73.59c-4.12,3.09-10.68,6.6-17.5,4.6-6.27-1.83-9.67-7.85-11.5-13.15"/><path class="cls-1" d="M70.62,39.23h0l18.32-10.42c2.77-1.8,4.36-4.61,4.36-7.23,0-1.12-.29-2.2-.91-3.14-2.05-3.16-6.93-3.63-10.89-1.06l-27.23,20.78"/><path class="cls-1" d="M46.89,52.73l-16.92,9.62-4.72,2.69-4.3,2.45c-2.07,1.18-3.62-1.13-1.72-2.58l4.91-3.75,3.72-2.83,17.32-13.21"/><path class="cls-1" d="M52.34,66.88c1.04,1.35,1.69,3.29,1.89,6.71h0c.06,.84,.08,1.75,.08,2.77l.13,23.21,4.78-4.99,.27-.28,5.26,5.21,.06,.06,4.99-5.27,5,5.27,1.91-1.97,3.19-3.3,2.34,2.18,3.3,3.09V58.39c0-9.25-6.35-17.01-14.93-19.17h0c-1.55-.39-3.16-.6-4.83-.6h-10.2"/><path class="cls-1" d="M85.64,73.08h-.08c-.54,0-3.75-.09-8.51-1.01"/><path class="cls-1" d="M55.88,64.26c4.42,2.51,8.72,4.3,12.62,5.59"/><path class="cls-1" d="M67.81,86.68c.5-7.73,1.04-15.96,1.05-16.04,0,0,0,0,0,0-.66-1.4-1.12-2.89-1.5-4.4-.65-2.62-1.03-5.29-1.25-7.98-.36-4.5-.85-11.8,5.75-11.49,2.86,.13,5.19,2.72,5.19,5.57v20.1l-5.51,15.02c-.77,2.09-3.87,1.43-3.73-.79Z"/><path class="cls-1" d="M58.29,40.53c1.52,1.54,2.38,3.6,2.64,5.73,.75,6.25-.41,12.59-4.59,17.48-1.82,2.13-4.92,4.47-7.9,3.4-2.5-.89-3.39-3.58-2.85-6.02l1.15-5.11,.17-3.74h-3.2l1.74-8.61c.42-2.08,1.68-3.94,3.55-4.95,3.24-1.74,6.9-.6,9.29,1.82Z"/><path class="cls-1" d="M46.74,55.98c7.21,3,12.71-9.24,12.71-13.95"/><line class="cls-1" x1="49.35" y1="47.03" x2="52.75" y2="47.03"/></svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><g><path class="cls-1" d="M45.06,43.89c-5.41-3.39-2.67-8.62-5.21-12.06-.45-.61-1.41-.41-1.61,.32-1.92,6.87-11.41,15.64,.71,20.62,3.73-.28,7.69-4.75,6.11-8.88Z"/><line class="cls-1" x1="33.96" y1="76.97" x2="52.97" y2="93.21"/><path class="cls-1" d="M34.24,77.49c-12.21,10.27-9.51,23.59-9.51,23.59"/><line class="cls-1" x1="45.76" y1="76.9" x2="33.96" y2="67.25"/><line class="cls-1" x1="50.53" y1="76.3" x2="55.48" y2="76.3"/><path class="cls-1" d="M45.13,43.94l1.04-.43c1.93-.79,3.99-1.2,6.07-1.2h.73"/><path class="cls-1" d="M60.02,76.9v-2.96c0-1.18-.96-2.14-2.14-2.14h-9.98c-1.18,0-2.14,.96-2.14,2.14v2.96"/><path class="cls-1" d="M36.11,51.5l-.35,.63c-1.18,2.09-1.8,4.45-1.8,6.86v17.99"/><path class="cls-1" d="M60.8,43.89c5.41-3.39,2.67-8.62,5.21-12.06,.45-.61,1.41-.41,1.61,.32,1.92,6.87,11.41,15.64-.71,20.62-3.73-.28-7.69-4.75-6.11-8.88Z"/><line class="cls-1" x1="71.9" y1="76.97" x2="52.89" y2="93.21"/><polyline class="cls-1" points="71.9 63.24 84.68 74.46 78.17 85.34"/><polyline class="cls-1" points="33.8 63.24 21.02 74.46 27.53 85.34"/><path class="cls-1" d="M71.62,77.49c12.21,10.27,9.51,23.59,9.51,23.59"/><line class="cls-1" x1="60.1" y1="76.9" x2="71.9" y2="67.25"/><path class="cls-1" d="M60.73,43.94l-1.04-.43c-1.93-.79-3.99-1.2-6.07-1.2h-.73"/><path class="cls-1" d="M69.75,51.5l.35,.63c1.18,2.09,1.8,4.45,1.8,6.86v17.99"/><line class="cls-1" x1="42.09" y1="59.77" x2="46.66" y2="59.77"/><line class="cls-1" x1="58.44" y1="59.77" x2="63.01" y2="59.77"/></g><line class="cls-1" x1="94.43" y1="27" x2="94.43" y2="104.66"/><path class="cls-1" d="M107.11,35.31v10.78c0,2.99-2.07,5.41-4.62,5.41h-16.01c-2.55,0-4.62-2.42-4.62-5.41v-10.78"/><g><line class="cls-1" x1="78.05" y1="40.94" x2="81.86" y2="35.31"/><line class="cls-1" x1="85.68" y1="40.94" x2="81.86" y2="35.31"/></g><g><line class="cls-1" x1="90.62" y1="32.63" x2="94.43" y2="27"/><line class="cls-1" x1="98.25" y1="32.63" x2="94.43" y2="27"/></g><g><line class="cls-1" x1="103.3" y1="39.24" x2="107.11" y2="33.61"/><line class="cls-1" x1="110.92" y1="39.24" x2="107.11" y2="33.61"/></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M55.92,84.43l-19.83,11.28c-2.5,1.42-5.67,.55-7.09-1.95L9.16,58.89c-1.42-2.5-.55-5.67,1.95-7.09l21.65-12.32c2.5-1.42,5.67-.55,7.09,1.95l7.19,12.64"/><g><g><line class="cls-1" x1="26.88" y1="64.37" x2="36.9" y2="58.68"/><line class="cls-1" x1="41.42" y1="80.79" x2="36.9" y2="58.68"/></g><line class="cls-1" x1="34.75" y1="70.4" x2="42.89" y2="65.77"/></g><g><path class="cls-1" d="M72.62,84.41l-20.33,.02c-2.87,0-5.2-2.32-5.2-5.19l-.05-40.13c0-2.87,2.32-5.2,5.19-5.2l24.91-.03c2.87,0,5.2,2.32,5.2,5.19l.02,14.18"/><path class="cls-1" d="M54.77,39.48c-.29-1.13-1.7-1.71-2.72-.91-.98,.76-.91,2.3,.03,3.55,.87,1.15,2.64,3.06,2.64,3.06,0,0,1.81-1.89,2.7-3.02,.96-1.23,1.05-2.76,.09-3.54-1.01-.81-2.43-.26-2.74,.86Z"/></g><rect class="cls-1" x="77.06" y="42.34" width="35.3" height="50.52" rx="5.2" ry="5.2" transform="translate(42.99 -36.64) rotate(28.09)"/><path class="cls-1" d="M95.58,44.52l-.11,.05c-.88,.83-3.99,2.35-4.94,2.52l-.06,.03v.07c.38,.89,.84,4.32,.64,5.51l.02,.13,.12-.05c.88-.83,3.99-2.34,4.94-2.52l.06-.03v-.07c-.38-.88-.84-4.32-.64-5.51l-.02-.12Z"/><path class="cls-1" d="M97.77,82.06l-.11,.05c-.88,.83-3.99,2.35-4.94,2.52l-.06,.03v.07c.38,.89,.84,4.32,.64,5.51l.02,.13,.12-.05c.88-.83,3.99-2.34,4.94-2.52l.06-.03v-.07c-.38-.88-.84-4.32-.64-5.51l-.02-.12Z"/><g><g><line class="cls-1" x1="59.67" y1="50.02" x2="71.71" y2="50.01"/><line class="cls-1" x1="64.42" y1="72.43" x2="71.71" y2="50.01"/></g><line class="cls-1" x1="63.71" y1="59.55" x2="73.5" y2="59.54"/></g><g><g><line class="cls-1" x1="94.12" y1="57.85" x2="104.74" y2="63.52"/><line class="cls-1" x1="87.73" y1="79.85" x2="104.74" y2="63.52"/></g><line class="cls-1" x1="93.19" y1="68.16" x2="101.82" y2="72.77"/></g><path class="cls-1" d="M19.59,55.97c-1.36-.56-3.25-.92-4.02-1.46l-.09-.03-.02,.09c.1,.93-.51,2.75-.69,4.21-.17,1.34,.32,2.79,1.65,3.03,.89,.16,1.87-.47,2.12-1.34,.47,.91,.45,2.06,.61,2.92l2.32-1.34c-.68-.56-1.69-1.12-2.27-1.97,.89,.2,1.92-.34,2.21-1.2,.44-1.28-.59-2.41-1.84-2.92Z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M658-648v-132H302v132h-60v-192h476v192h-60Zm-518 60h680-680Zm599 95q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm-81 313v-192H302v192h356Zm60 60H242v-176H80v-246q0-45.05 30.5-75.525Q141-648 186-648h588q45.05 0 75.525 30.475Q880-587.05 880-542v246H718v176Zm102-236v-186.215Q820-562 806.775-575 793.55-588 774-588H186q-19.55 0-32.775 13.225Q140-561.55 140-542v186h102v-76h476v76h102Z"/></svg>

After

Width:  |  Height:  |  Size: 506 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 22" fill="#84858D">
<path d="M11 9a3 3 0 0 0-2.822 2.02L7 11.014A3.02 3.02 0 0 1 4 8V6.816a3 3 0 1 0-2 0v8.368a3 3 0 1 0 2 0v-3.2a4.962 4.962 0 0 0 3 1.03l1.2.006A2.995 2.995 0 1 0 11 9Z"/>
</svg>

After

Width:  |  Height:  |  Size: 254 B

87642
build/assets/nexign.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -1 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 66.5 66.5"><polygon points="65.2 65.04 39.86 1.47 37.69 1.47 27.52 1.47 25.65 1.47 0 65.04 13.35 65.04 32.54 14.9 51.31 65.04 65.2 65.04" fill="#4e5463"/></svg>
<svg width="72" height="73" viewBox="0 0 72 73" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" width="41" height="14" rx="7" fill="url(#paint0_linear_418_19)"/>
<rect x="4" y="21" width="64" height="14" rx="7" fill="url(#paint1_linear_418_19)"/>
<rect x="4" y="55" width="64" height="14" rx="7" fill="url(#paint2_linear_418_19)"/>
<rect x="4" y="38" width="47" height="14" rx="7" fill="url(#paint3_linear_418_19)"/>
<defs>
<linearGradient id="paint0_linear_418_19" x1="-3" y1="18" x2="40.9902" y2="33.2394" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint1_linear_418_19" x1="-6.92683" y1="35" x2="52.5804" y2="67.1793" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint2_linear_418_19" x1="-6.92683" y1="69" x2="52.5804" y2="101.179" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint3_linear_418_19" x1="-4.02439" y1="52" x2="44.7615" y2="71.374" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 1.3 KiB

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>ASSAYO</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.0fa5ec0a.js"></script><link href="./static/css/main.c2f3798a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script type="text/javascript">!function(e,t,c,n,r,a,s){e[r]=e[r]||function(){(e[r].a=e[r].a||[]).push(arguments)},e[r].l=1*new Date;for(var i=0;i<document.scripts.length;i++)if(document.scripts[i].src===n)return;a=t.createElement(c),s=t.getElementsByTagName(c)[0],a.async=1,a.src=n,s.parentNode.insertBefore(a,s)}(window,document,"script","https://mc.yandex.ru/metrika/tag.js","ym"),ym(94903985,"init",{clickmap:!0,trackLinks:!0,accurateTrackBounce:!0,webvisor:!0})</script><noscript><div><img src="https://mc.yandex.ru/watch/94903985" style="position:absolute;left:-9999px" alt=""/></div></noscript></body></html>
<!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.d0ec1697.js"></script><link href="./static/css/main.48701252.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script type="text/javascript">!function(e,t,c,n,r,a,s){e[r]=e[r]||function(){(e[r].a=e[r].a||[]).push(arguments)},e[r].l=1*new Date;for(var i=0;i<document.scripts.length;i++)if(document.scripts[i].src===n)return;a=t.createElement(c),s=t.getElementsByTagName(c)[0],a.async=1,a.src=n,s.parentNode.insertBefore(a,s)}(window,document,"script","https://mc.yandex.ru/metrika/tag.js","ym"),ym(94903985,"init",{clickmap:!0,trackLinks:!0,accurateTrackBounce:!0,webvisor:!0})</script><noscript><div><img src="https://mc.yandex.ru/watch/94903985" style="position:absolute;left:-9999px" alt=""/></div></noscript></body></html>

View file

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

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,74 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @remix-run/router v1.3.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router DOM v6.8.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router v6.8.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/

File diff suppressed because one or more lines are too long

46
nginx.conf Normal file
View file

@ -0,0 +1,46 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
client_max_body_size 100M;
sendfile on;
keepalive_timeout 65;
port_in_redirect off;
gzip on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private auth;
gzip_types font/woff2 image/svg+xml text/plain text/css text/xml text/javascript application/javascript application/xml;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
root html;
index index.html index.htm;
server {
listen 8000;
server_name localhost;
client_max_body_size 100M;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Frontend
location / {
try_files $uri $uri/ /index.html;
}
}
}

View file

@ -1,5 +1,5 @@
{
"name": "my",
"name": "Assayo",
"version": "0.1.0",
"homepage": ".",
"private": true,

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M82.26,96.48v14.68h-10.63v-2.15c0-1.91,1.55-3.45,3.45-3.45h1.66v-7.97"/><path class="cls-1" d="M65.18,99.19v11.97h-10.63v-2.15c0-1.91,1.55-3.45,3.45-3.45h1.66v-11.01"/><path class="cls-1" d="M29.96,62.35c1.61,2.73,3.48,5.17,5.17,5.46,2.78,.47,8.19-4.9,10.65-7.55"/><path class="cls-1" d="M24.13,61.16c-.71-3.01-.89-5.2-.89-5.2-.22-2.61,1.86-3.25,2.87-1.58,0,0,.68,1.72,1.74,3.94"/><path class="cls-1" d="M54.24,73.59c-4.12,3.09-10.68,6.6-17.5,4.6-6.27-1.83-9.67-7.85-11.5-13.15"/><path class="cls-1" d="M70.62,39.23h0l18.32-10.42c2.77-1.8,4.36-4.61,4.36-7.23,0-1.12-.29-2.2-.91-3.14-2.05-3.16-6.93-3.63-10.89-1.06l-27.23,20.78"/><path class="cls-1" d="M46.89,52.73l-16.92,9.62-4.72,2.69-4.3,2.45c-2.07,1.18-3.62-1.13-1.72-2.58l4.91-3.75,3.72-2.83,17.32-13.21"/><path class="cls-1" d="M52.34,66.88c1.04,1.35,1.69,3.29,1.89,6.71h0c.06,.84,.08,1.75,.08,2.77l.13,23.21,4.78-4.99,.27-.28,5.26,5.21,.06,.06,4.99-5.27,5,5.27,1.91-1.97,3.19-3.3,2.34,2.18,3.3,3.09V58.39c0-9.25-6.35-17.01-14.93-19.17h0c-1.55-.39-3.16-.6-4.83-.6h-10.2"/><path class="cls-1" d="M85.64,73.08h-.08c-.54,0-3.75-.09-8.51-1.01"/><path class="cls-1" d="M55.88,64.26c4.42,2.51,8.72,4.3,12.62,5.59"/><path class="cls-1" d="M67.81,86.68c.5-7.73,1.04-15.96,1.05-16.04,0,0,0,0,0,0-.66-1.4-1.12-2.89-1.5-4.4-.65-2.62-1.03-5.29-1.25-7.98-.36-4.5-.85-11.8,5.75-11.49,2.86,.13,5.19,2.72,5.19,5.57v20.1l-5.51,15.02c-.77,2.09-3.87,1.43-3.73-.79Z"/><path class="cls-1" d="M58.29,40.53c1.52,1.54,2.38,3.6,2.64,5.73,.75,6.25-.41,12.59-4.59,17.48-1.82,2.13-4.92,4.47-7.9,3.4-2.5-.89-3.39-3.58-2.85-6.02l1.15-5.11,.17-3.74h-3.2l1.74-8.61c.42-2.08,1.68-3.94,3.55-4.95,3.24-1.74,6.9-.6,9.29,1.82Z"/><path class="cls-1" d="M46.74,55.98c7.21,3,12.71-9.24,12.71-13.95"/><line class="cls-1" x1="49.35" y1="47.03" x2="52.75" y2="47.03"/></svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><g><path class="cls-1" d="M45.06,43.89c-5.41-3.39-2.67-8.62-5.21-12.06-.45-.61-1.41-.41-1.61,.32-1.92,6.87-11.41,15.64,.71,20.62,3.73-.28,7.69-4.75,6.11-8.88Z"/><line class="cls-1" x1="33.96" y1="76.97" x2="52.97" y2="93.21"/><path class="cls-1" d="M34.24,77.49c-12.21,10.27-9.51,23.59-9.51,23.59"/><line class="cls-1" x1="45.76" y1="76.9" x2="33.96" y2="67.25"/><line class="cls-1" x1="50.53" y1="76.3" x2="55.48" y2="76.3"/><path class="cls-1" d="M45.13,43.94l1.04-.43c1.93-.79,3.99-1.2,6.07-1.2h.73"/><path class="cls-1" d="M60.02,76.9v-2.96c0-1.18-.96-2.14-2.14-2.14h-9.98c-1.18,0-2.14,.96-2.14,2.14v2.96"/><path class="cls-1" d="M36.11,51.5l-.35,.63c-1.18,2.09-1.8,4.45-1.8,6.86v17.99"/><path class="cls-1" d="M60.8,43.89c5.41-3.39,2.67-8.62,5.21-12.06,.45-.61,1.41-.41,1.61,.32,1.92,6.87,11.41,15.64-.71,20.62-3.73-.28-7.69-4.75-6.11-8.88Z"/><line class="cls-1" x1="71.9" y1="76.97" x2="52.89" y2="93.21"/><polyline class="cls-1" points="71.9 63.24 84.68 74.46 78.17 85.34"/><polyline class="cls-1" points="33.8 63.24 21.02 74.46 27.53 85.34"/><path class="cls-1" d="M71.62,77.49c12.21,10.27,9.51,23.59,9.51,23.59"/><line class="cls-1" x1="60.1" y1="76.9" x2="71.9" y2="67.25"/><path class="cls-1" d="M60.73,43.94l-1.04-.43c-1.93-.79-3.99-1.2-6.07-1.2h-.73"/><path class="cls-1" d="M69.75,51.5l.35,.63c1.18,2.09,1.8,4.45,1.8,6.86v17.99"/><line class="cls-1" x1="42.09" y1="59.77" x2="46.66" y2="59.77"/><line class="cls-1" x1="58.44" y1="59.77" x2="63.01" y2="59.77"/></g><line class="cls-1" x1="94.43" y1="27" x2="94.43" y2="104.66"/><path class="cls-1" d="M107.11,35.31v10.78c0,2.99-2.07,5.41-4.62,5.41h-16.01c-2.55,0-4.62-2.42-4.62-5.41v-10.78"/><g><line class="cls-1" x1="78.05" y1="40.94" x2="81.86" y2="35.31"/><line class="cls-1" x1="85.68" y1="40.94" x2="81.86" y2="35.31"/></g><g><line class="cls-1" x1="90.62" y1="32.63" x2="94.43" y2="27"/><line class="cls-1" x1="98.25" y1="32.63" x2="94.43" y2="27"/></g><g><line class="cls-1" x1="103.3" y1="39.24" x2="107.11" y2="33.61"/><line class="cls-1" x1="110.92" y1="39.24" x2="107.11" y2="33.61"/></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.83 128.83"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.93px;}</style></defs><path class="cls-1" d="M55.92,84.43l-19.83,11.28c-2.5,1.42-5.67,.55-7.09-1.95L9.16,58.89c-1.42-2.5-.55-5.67,1.95-7.09l21.65-12.32c2.5-1.42,5.67-.55,7.09,1.95l7.19,12.64"/><g><g><line class="cls-1" x1="26.88" y1="64.37" x2="36.9" y2="58.68"/><line class="cls-1" x1="41.42" y1="80.79" x2="36.9" y2="58.68"/></g><line class="cls-1" x1="34.75" y1="70.4" x2="42.89" y2="65.77"/></g><g><path class="cls-1" d="M72.62,84.41l-20.33,.02c-2.87,0-5.2-2.32-5.2-5.19l-.05-40.13c0-2.87,2.32-5.2,5.19-5.2l24.91-.03c2.87,0,5.2,2.32,5.2,5.19l.02,14.18"/><path class="cls-1" d="M54.77,39.48c-.29-1.13-1.7-1.71-2.72-.91-.98,.76-.91,2.3,.03,3.55,.87,1.15,2.64,3.06,2.64,3.06,0,0,1.81-1.89,2.7-3.02,.96-1.23,1.05-2.76,.09-3.54-1.01-.81-2.43-.26-2.74,.86Z"/></g><rect class="cls-1" x="77.06" y="42.34" width="35.3" height="50.52" rx="5.2" ry="5.2" transform="translate(42.99 -36.64) rotate(28.09)"/><path class="cls-1" d="M95.58,44.52l-.11,.05c-.88,.83-3.99,2.35-4.94,2.52l-.06,.03v.07c.38,.89,.84,4.32,.64,5.51l.02,.13,.12-.05c.88-.83,3.99-2.34,4.94-2.52l.06-.03v-.07c-.38-.88-.84-4.32-.64-5.51l-.02-.12Z"/><path class="cls-1" d="M97.77,82.06l-.11,.05c-.88,.83-3.99,2.35-4.94,2.52l-.06,.03v.07c.38,.89,.84,4.32,.64,5.51l.02,.13,.12-.05c.88-.83,3.99-2.34,4.94-2.52l.06-.03v-.07c-.38-.88-.84-4.32-.64-5.51l-.02-.12Z"/><g><g><line class="cls-1" x1="59.67" y1="50.02" x2="71.71" y2="50.01"/><line class="cls-1" x1="64.42" y1="72.43" x2="71.71" y2="50.01"/></g><line class="cls-1" x1="63.71" y1="59.55" x2="73.5" y2="59.54"/></g><g><g><line class="cls-1" x1="94.12" y1="57.85" x2="104.74" y2="63.52"/><line class="cls-1" x1="87.73" y1="79.85" x2="104.74" y2="63.52"/></g><line class="cls-1" x1="93.19" y1="68.16" x2="101.82" y2="72.77"/></g><path class="cls-1" d="M19.59,55.97c-1.36-.56-3.25-.92-4.02-1.46l-.09-.03-.02,.09c.1,.93-.51,2.75-.69,4.21-.17,1.34,.32,2.79,1.65,3.03,.89,.16,1.87-.47,2.12-1.34,.47,.91,.45,2.06,.61,2.92l2.32-1.34c-.68-.56-1.69-1.12-2.27-1.97,.89,.2,1.92-.34,2.21-1.2,.44-1.28-.59-2.41-1.84-2.92Z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M658-648v-132H302v132h-60v-192h476v192h-60Zm-518 60h680-680Zm599 95q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm-81 313v-192H302v192h356Zm60 60H242v-176H80v-246q0-45.05 30.5-75.525Q141-648 186-648h588q45.05 0 75.525 30.475Q880-587.05 880-542v246H718v176Zm102-236v-186.215Q820-562 806.775-575 793.55-588 774-588H186q-19.55 0-32.775 13.225Q140-561.55 140-542v186h102v-76h476v76h102Z"/></svg>

After

Width:  |  Height:  |  Size: 506 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 22" fill="#84858D">
<path d="M11 9a3 3 0 0 0-2.822 2.02L7 11.014A3.02 3.02 0 0 1 4 8V6.816a3 3 0 1 0-2 0v8.368a3 3 0 1 0 2 0v-3.2a4.962 4.962 0 0 0 3 1.03l1.2.006A2.995 2.995 0 1 0 11 9Z"/>
</svg>

After

Width:  |  Height:  |  Size: 254 B

87642
public/assets/nexign.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -1 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 66.5 66.5"><polygon points="65.2 65.04 39.86 1.47 37.69 1.47 27.52 1.47 25.65 1.47 0 65.04 13.35 65.04 32.54 14.9 51.31 65.04 65.2 65.04" fill="#4e5463"/></svg>
<svg width="72" height="73" viewBox="0 0 72 73" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" width="41" height="14" rx="7" fill="url(#paint0_linear_418_19)"/>
<rect x="4" y="21" width="64" height="14" rx="7" fill="url(#paint1_linear_418_19)"/>
<rect x="4" y="55" width="64" height="14" rx="7" fill="url(#paint2_linear_418_19)"/>
<rect x="4" y="38" width="47" height="14" rx="7" fill="url(#paint3_linear_418_19)"/>
<defs>
<linearGradient id="paint0_linear_418_19" x1="-3" y1="18" x2="40.9902" y2="33.2394" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint1_linear_418_19" x1="-6.92683" y1="35" x2="52.5804" y2="67.1793" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint2_linear_418_19" x1="-6.92683" y1="69" x2="52.5804" y2="101.179" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
<linearGradient id="paint3_linear_418_19" x1="-4.02439" y1="52" x2="44.7615" y2="71.374" gradientUnits="userSpaceOnUse">
<stop offset="0.250021" stop-color="#4F0FDE"/>
<stop offset="1" stop-color="#D18FF9"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -35,7 +35,7 @@
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>ASSAYO</title>
<title>Git статистика</title>
<meta name="description" content="Простой и быстрый отчёт по истории коммитов в git.">
<meta name="keywords" content="git, статистика, аудит, история, log, мониторинг, контроль сотрудников">
<meta name="author" content="Bakhirev Aleksei">

View file

@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M658-648v-132H302v132h-60v-192h476v192h-60Zm-518 60h680-680Zm599 95q12 0 21-9t9-21q0-12-9-21t-21-9q-12 0-21 9t-9 21q0 12 9 21t21 9Zm-81 313v-192H302v192h356Zm60 60H242v-176H80v-246q0-45.05 30.5-75.525Q141-648 186-648h588q45.05 0 75.525 30.475Q880-587.05 880-542v246H718v176Zm102-236v-186.215Q820-562 806.775-575 793.55-588 774-588H186q-19.55 0-32.775 13.225Q140-561.55 140-542v186h102v-76h476v76h102Z"/></svg>

After

Width:  |  Height:  |  Size: 506 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 22" fill="#84858D">
<path d="M11 9a3 3 0 0 0-2.822 2.02L7 11.014A3.02 3.02 0 0 1 4 8V6.816a3 3 0 1 0-2 0v8.368a3 3 0 1 0 2 0v-3.2a4.962 4.962 0 0 0 3 1.03l1.2.006A2.995 2.995 0 1 0 11 9Z"/>
</svg>

After

Width:  |  Height:  |  Size: 254 B

View file

@ -6,6 +6,7 @@ import ru from 'ts/config/translations/ru';
import Authorization from 'ts/pages/Authorization';
import userSettings from 'ts/store/UserSettings';
import Notifications from 'ts/components/Notifications';
import printStore from 'ts/pages/PageWrapper/store/Print';
import './styles/index.scss';
@ -30,6 +31,9 @@ function getParametersFromString(text: string) {
function renderReactApplication() {
// @ts-ignore
console.log(window?.report?.length);
window.onafterprint = () => {
printStore.endPrint();
};
render(
<React.StrictMode>
<HashRouter>

View file

@ -31,12 +31,21 @@ function getSortedContent(content: any[], sortRules: ISort[]) {
});
}
export default function getFakeLoader(
interface IFakeLoader {
content?: any,
pagination?: IPaginationRequest,
query?: string,
mode?: string,
sort?: ISort[],
) {
}
export default function getFakeLoader({
content,
pagination,
query,
mode,
sort,
}: IFakeLoader) {
const formattedContent = content || [];
const filteredContent = query
? formattedContent.filter((item:any) => item.name.toLowerCase().includes(query?.toLowerCase()))
@ -46,7 +55,7 @@ export default function getFakeLoader(
? getSortedContent(filteredContent, sort || [])
: filteredContent;
if (!pagination) {
if (!pagination || mode === 'print') {
return Promise.resolve({
size: sortedContent?.length || 0,
number: 0,

View file

@ -0,0 +1,26 @@
@import '../../../styles/variables';
.external_link {
font-size: var(--font-s);
font-weight: 100;
display: inline-block;
padding: 0;
margin: 0 var(--space-s) 0 0;
box-sizing: border-box;
background-color: transparent;
border: none;
white-space: normal;
text-overflow: ellipsis;
line-height: var(--font-s);
cursor: pointer;
text-decoration: underline;
color: var(--color-first);
&:hover {
text-decoration: none;
}
}

View file

@ -0,0 +1,23 @@
import React from 'react';
import { Link } from 'react-router-dom';
import style from './index.module.scss';
interface IExternalLinkProps {
link: string,
text: string,
}
function ExternalLink({ link, text }: IExternalLinkProps) {
return (
<Link
to={link}
target="_blank"
className={style.external_link}
>
{text}
</Link>
);
}
export default ExternalLink;

View file

@ -0,0 +1,43 @@
import React from 'react';
import ICommit from 'ts/interfaces/Commit';
import ExternalLink from 'ts/components/ExternalLink';
import userSettings from 'ts/store/UserSettings';
import dataGrip from 'ts/helpers/DataGrip';
import style from '../styles/index.module.scss';
interface IGetItemProps {
commit: ICommit;
mode?: string;
}
function GetItem({ commit, mode }: IGetItemProps) {
const size = commit.taskNumber?.length || 1;
const className = size > 5
? style.get_list_big_number
: '';
const prId = dataGrip.pr.prByTask[commit.task];
return (
<div className={style.get_list}>
<div className={style.get_list_title}>
<ExternalLink
link={`${userSettings?.settings?.linksPrefix?.task || '/'}${commit.task}`}
text={commit.task}
/>
{prId && mode !== 'print' && (
<ExternalLink
link={`${userSettings?.settings?.linksPrefix?.pr || '/'}${prId}`}
text="PR"
/>
)}
</div>
<div className={`${style.get_list_icon} ${className}`}>
{commit.taskNumber}
</div>
</div>
);
}
export default GetItem;

View file

@ -0,0 +1,29 @@
import React from 'react';
import ICommit from 'ts/interfaces/Commit';
import GetItem from './components/Item';
import style from './styles/index.module.scss';
interface IAchievementsProps {
list: ICommit[];
mode?: string;
}
function GetList({ list, mode }: IAchievementsProps) {
const items = list?.map((commit: ICommit) => (
<GetItem
key={commit.taskNumber}
commit={commit}
mode={mode}
/>
));
return (
<div className={style.get_list_container}>
{items}
</div>
);
}
export default GetList;

View file

@ -0,0 +1,77 @@
@import '../../../../styles/variables';
.get_list_container {
margin: 12px 0 24px 0;
}
.get_list {
display: inline-block;
padding: 0;
margin: 0 var(--space-sm) var(--space-sm) 0;
box-sizing: border-box;
vertical-align: top;
&:last-child {
margin: 0 var(--space-sm) 0 0;
}
&_title {
position: relative;
font-size: var(--font-s);
display: block;
margin: 0 0 var(--space-s) 0;
padding: var(--space-xxl) var(--space-s);
text-align: center;
box-sizing: border-box;
border-radius: var(--border-radius-s);
border: 1px solid var(--color-border);
background-color: white;
&:before {
position: absolute;
bottom: -6px;
left: calc(50% - 6px);
z-index: 0;
content: '';
display: block;
padding: 0;
margin: 0;
width: var(--space-m);
height: var(--space-m);
box-sizing: border-box;
transform: rotateZ(45deg);
background-color: white;
border-right: 1px solid var(--color-border);
border-bottom: 1px solid var(--color-border);
}
}
&_icon {
font-size: var(--font-m);
font-weight: bold;
display: block;
width: 64px;
height: 64px;
margin: 0 auto;
line-height: 64px;
text-align: center;
white-space: nowrap;
border-radius: 32px;
letter-spacing: 2px;
color: var(--color-black);
background-color: var(--color-border);
}
&_big_number {
letter-spacing: normal;
}
}

View file

@ -43,12 +43,13 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-webkit-print-color-adjust: exact;
}
.day_time,
.day_name {
border-bottom: 1px solid grey;
box-sizing: border-box;
border-bottom: 1px solid grey;
}
.day_name {

View file

@ -26,6 +26,7 @@
text-align: center;
border-radius: 6px;
border: 1px solid #FFFFFF;
-webkit-print-color-adjust: exact;
}
&_work {

View file

@ -28,6 +28,7 @@
text-align: left;
color: #FFFFFF;
background-color: #D0D1D2;
-webkit-print-color-adjust: exact;
}
.line_chart_sub_item {

View file

@ -21,7 +21,7 @@ const Header = observer(({
className={`${style.modal_window_title} ${className || ''}`}
>
{children}
{children && onClose ? (
{onClose ? (
<img
id={`${id}-close`}
className={`${style.modal_window_close} ${className || ''}`}

View file

@ -6,6 +6,7 @@
text-align: left;
box-sizing: border-box;
white-space: nowrap;
break-inside: auto;
&_white {
position: relative;
@ -16,6 +17,7 @@
white-space: normal;
box-sizing: border-box;
vertical-align: top;
break-inside: auto;
border-radius: 8px;
border: 1px solid var(--color-border);
background-color: #FFFFFF;
@ -37,6 +39,7 @@
margin: 0;
white-space: normal;
vertical-align: top;
break-inside: auto;
border: 1px solid transparent;
background-color: transparent;
}
@ -60,7 +63,7 @@
margin-left: 24px;
}
@media (max-width: 1200px) {
@media screen and (max-width: 1200px) {
.main_wrapper {
&_item {
display: block;
@ -76,3 +79,13 @@
margin: 0 0 24px 0;
}
}
@media print {
.main_wrapper {
&_white {
padding: 0;
border-radius: 0;
border: none;
}
}
}

View file

@ -0,0 +1,3 @@
export default interface ICommonPageProps {
mode?: string;
}

View file

@ -22,7 +22,7 @@ function Card({
}
const className = {
info: style.card_fact,
info: style.card_info,
fact: style.card_fact,
warning: style.card_warning,
error: style.card_error,

View file

@ -98,6 +98,13 @@
background-size: 100% auto;
}
.card_info {
--color-temp-border: #97C2A9;
--color-temp-icon: url('/assets/recommendations/info.svg');
--color-temp-bg: #E3F8EC;
--color-temp-title: #58866B;
}
.card_fact {
--color-temp-border: var(--color-11);
--color-temp-icon: url('/assets/recommendations/info.svg');

View file

@ -16,6 +16,7 @@ function Column({
isResizable,
isDraggable,
isShow,
minWidth,
width,
onClick,
}: IColumn): JSX.Element {
@ -35,6 +36,7 @@ function Column({
isResizable,
isDraggable,
isShow,
minWidth,
width,
onClick,
}}
@ -54,6 +56,7 @@ Column.defaultProps = {
isResizable: false,
isDraggable: false,
isShow: true,
minWidth: undefined,
width: undefined,
onClick: undefined,
};

View file

@ -1,12 +1,12 @@
import type { IColumn } from '../interfaces/Column';
export default function getDefaultColumnWidth(
export default function getAdaptiveColumnWidth(
columns: IColumn[],
offsetWidth: number,
): number {
if (!offsetWidth) return 150;
const visibleColumns = columns.filter(({ isShow }: IColumn) => isShow);
const visibleColumns = columns.filter(({ isShow }: IColumn) => isShow);
const columnsWidth = visibleColumns.map((column: IColumn) => (
column.userWidth || column.defaultWidth || 0
));
@ -15,8 +15,16 @@ export default function getDefaultColumnWidth(
if (!adaptiveColumnsCount) return 40;
const tableWidth = offsetWidth - fixedWidth;
const adaptiveColumnsWidth = tableWidth / adaptiveColumnsCount;
console.dir(adaptiveColumnsWidth);
// если адаптив < минималки, то адаптив делает перерасчет
let adaptiveTableWidth = tableWidth;
let adaptiveColumnsWidth = adaptiveTableWidth / adaptiveColumnsCount;
visibleColumns.forEach((column: IColumn) => {
if (!column.minWidth
|| column.minWidth < adaptiveColumnsWidth) return;
adaptiveTableWidth -= column.minWidth;
adaptiveColumnsWidth = adaptiveTableWidth / adaptiveColumnsCount;
});
return Math.max(adaptiveColumnsWidth, 40);
}

View file

@ -3,7 +3,7 @@ import { IColumn } from '../interfaces/Column';
function getColumnConfigs(
dirtyColumns: IColumn[] = [],
defaultWidth?: number,
adaptiveWidth: number = 150,
sort?: ISort[],
): IColumn[] {
const sortByColumns = sort?.reduce((ref: any, item: ISort) => {
@ -11,13 +11,19 @@ function getColumnConfigs(
return ref;
}, {});
const columns: IColumn[] = dirtyColumns.map((column: IColumn) => ({
...column,
sortDirection: typeof column?.isSortable === 'string'
? (sortByColumns[column?.isSortable || ''] || 0)
: (sortByColumns[column?.properties || ''] || 0),
width: column.userWidth || column.defaultWidth || defaultWidth || column.width || 150,
}));
const columns: IColumn[] = dirtyColumns.map((column: IColumn) => {
const adaptiveColumnWidth = column.minWidth
? Math.max(column.minWidth, adaptiveWidth)
: adaptiveWidth;
return {
...column,
sortDirection: typeof column?.isSortable === 'string'
? (sortByColumns[column?.isSortable || ''] || 0)
: (sortByColumns[column?.properties || ''] || 0),
width: column.userWidth || column.defaultWidth || adaptiveColumnWidth, // || column.width || 150,
};
});
const middle = Math.floor(columns.length / 2);
return [

View file

@ -23,6 +23,9 @@ export default function getDefaultProps(children: React.ReactNode) {
[ColumnTypesEnum.SHORT_NUMBER]: 70,
}[template || ''] || 0;
// @ts-ignore
const minWidth = child?.props?.minWidth || 40;
// @ts-ignore
const isSortable = child?.props?.isSortable // @ts-ignore
? child?.props?.isSortable
@ -33,9 +36,10 @@ export default function getDefaultProps(children: React.ReactNode) {
className,
template,
isSortable,
minWidth,
defaultWidth,
width: undefined,
userWidth: undefined,
defaultWidth,
};
});
}

View file

@ -5,7 +5,7 @@ import ISort from 'ts/interfaces/Sort';
import { IColumn } from './interfaces/Column';
import Header from './components/Header';
import Body from './components/Body';
import getDefaultColumnWidth from './helpers/getDefaultColumnWidth';
import getAdaptiveColumnWidth from './helpers/getAdaptiveColumnWidth';
import getColumnConfigs from './helpers/getColumnConfigs';
import getDefaultProps from './helpers/getDefaultProps';
@ -38,8 +38,8 @@ function Table({
}, [currentWidth]);
const defaultColumns = getDefaultProps(children) as IColumn[];
const defaultWidth = getDefaultColumnWidth(defaultColumns, offsetWidth);
const columns = getColumnConfigs(defaultColumns, defaultWidth, sort);
const adaptiveWidth = getAdaptiveColumnWidth(defaultColumns, offsetWidth);
const columns = getColumnConfigs(defaultColumns, adaptiveWidth, sort);
return (
<div

View file

@ -38,6 +38,8 @@ export interface IColumn {
className?: string | Function
/** Стилья для колонки */
style?: Function,
/** Минимальная ширина столбца если он адаптивен */
minWidth?: number,
/** Ширина столбца заданная в верстке */
defaultWidth?: number,
/** Ширина столбца установленная пользователем */

View file

@ -17,6 +17,7 @@
.table {
display: block;
width: fit-content;
break-inside: auto;
--table-cell-height: 48px;
--table-bar-width: 350px;
}
@ -32,6 +33,7 @@
display: block;
white-space: nowrap;
border-bottom: 1px solid #EEEEEE;
break-inside: auto;
}
.table_row:last-child {
@ -89,4 +91,17 @@
.disabled {
opacity: 0.4;
filter: grayscale(0.6);
}
@media (max-width: 800px) {
.table_cell:first-child,
.table_header_cell:first-child {
position: static;
}
}
@media print {
.table_cell:first-child {
background-color: transparent;
}
}

View file

@ -24,7 +24,7 @@ function Column({ dayInfo, order, author }: IColumnProps) {
<div
key={name}
>
<Author name={name} />
{author ? null : (<Author name={name} />)}
<Chart tasks={tasks as IHashMap<ICommit[]>} />
<Tasks tasks={tasks as IHashMap<ICommit[]>} />
</div>

View file

@ -0,0 +1,56 @@
import React from 'react';
import { IUiKitWrapperProps } from './Wrapper';
import UiKitButton from './Button';
import UiKitSelect from './Select';
interface IUiKitSelectWithButtonsProps extends IUiKitWrapperProps {
className?: string;
value: any;
options: any[];
onChange: Function;
}
function UiKitSelectWithButtons({
className,
value,
options,
onChange,
}: IUiKitSelectWithButtonsProps) {
let index = options.map((item: any) => item.id).indexOf(value);
if (index === -1) index = 0;
return (
<>
<UiKitButton
type="second"
disabled={index <= 0}
onClick={() => {
onChange(options[index - 1]?.id);
}}
>
«
</UiKitButton>
<UiKitSelect
value={value}
options={options}
className={className}
onChange={onChange}
/>
<UiKitButton
type="second"
disabled={index >= (options.length - 1)}
onClick={() => {
onChange(options[index + 1]?.id);
}}
>
»
</UiKitButton>
</>
);
}
UiKitSelectWithButtons.defaultProps = {
className: '',
};
export default UiKitSelectWithButtons;

View file

@ -116,7 +116,7 @@
}
.button + .button {
margin: 0 0 0 24px;
margin-right: 24px;
}
.ui_kit_select {

View file

@ -41,12 +41,14 @@ interface IMonthProps {
max: IHashMap<number>;
month: IMonth;
showEvents: boolean;
hideMoney?: boolean;
}
function Month({
max,
month,
showEvents,
hideMoney,
}: IMonthProps): React.ReactElement | null {
const tasksChart = getOptions({ max: max.tasks, suffix: 'задач' });
const moneyChart = getOptions({
@ -55,7 +57,6 @@ function Month({
formatter: getShortMoney,
});
console.dir(month);
return (
<div className={style.year_chart_month}>
<Header month={month}/>
@ -64,11 +65,13 @@ function Month({
maxCommits={max.commits}
showEvents={showEvents}
/>
<MonthTotal
title="$"
options={moneyChart}
value={month.money}
/>
{!hideMoney && (
<MonthTotal
title="$"
options={moneyChart}
value={month.money}
/>
)}
<MonthTotal
title="☑"
options={tasksChart}
@ -78,4 +81,8 @@ function Month({
);
}
Month.defaultProps = {
hideMoney: false,
};
export default Month;

View file

@ -24,6 +24,7 @@ function YearChart({
const authorsByDate = getAuthorByDate(authors);
const months = getCommitsByMonth(wordDays, authorsByDate);
const hideMoney = authors?.length === 1;
const max = {
tasks: new MinMaxCounter(),
@ -45,6 +46,7 @@ function YearChart({
}}
month={month}
showEvents={showEvents}
hideMoney={hideMoney}
/>
));

View file

@ -50,11 +50,13 @@
height: var(--day-size);
margin: 0 1px 1px 0;
vertical-align: top;
background-color: var(--color-border);
background-blend-mode: screen;
cursor: pointer;
text-align: center;
background-color: var(--color-border);
background-blend-mode: screen;
-webkit-print-color-adjust: exact;
&_arrow {
position: absolute;
top: 20px;

View file

@ -0,0 +1,39 @@
@import '../../../../styles/variables';
.year_chart_month {
&_info {
display: block;
white-space: nowrap;
}
&_text {
font-weight: 100;
font-size: var(--font-xs);
font-family: Arial, Verdana, sans-serif;
display: inline-block;
width: 16px;
padding: 0;
margin: 0;
text-align: left;
line-height: var(--font-xs);
text-decoration: none;
vertical-align: middle;
color: var(--color-11);
}
&_chart {
display: inline-block;
width: 94px;
height: var(--space-s);
margin: 0;
border-radius: 0;
vertical-align: middle;
> div:last-child {
border-radius: 0;
background-color: #C2CEE4;
}
}
}

View file

@ -171,6 +171,15 @@ git может показать малое количество изменени
- находим среднее арифметическое;
- если результат меньше 1.3 считаем, что паралельных работ в рамках большинства фичей обычно нет;
# Почему это плохо:
- повышается bus factor;
- сотрудники медленее развиваются;
- трудно качественно проверить работу сотрудника;
# Почему это хорошо:
- появляюся эксперты, которые очень глубоко погружены в предметную область и могут предложить более качественные решения;
- скорее всего не бывает merge конфликтов;
- проект может очень быстро паралельно развиваться в разные стороны;
§ recommendations.scope.parallelism.has.title
Часть работ паралельно
@ -204,7 +213,13 @@ git может показать малое количество изменени
Изменить зарплату каждого разработчика, для более точной суммы, можно в разделе «Настройки»
# Это много или мало?
Для ответа на этот вопрос, нужно ответить на следующие:
- Можно ли за эти деньги было купить готовое решение?
- Можно ли за эти деньги сделать более хороший продукт?
Если ответ на оба вопроса «да», то возможно, разработка с нуля не стоила потраченных на неё денег.
§ recommendations.scope.bus.everyHasOne.title
Bus factor = 1
@ -220,7 +235,6 @@ Bus factor = 1
- более 80% коммитов в фичу делает один человек;
- проект имеет более 60% таких фичей;
§ recommendations.scope.bus.oneMaintainer
в фичи погружен один человек.
@ -232,14 +246,12 @@ Bus factor = 1
# Как делается выборка:
- более 80% коммитов в фичу сделал один человек;
§ recommendations.scope.types.process.title
Плохие процессы
§ recommendations.scope.types.process.description
Большинство фич содержат один тип задач.
§ recommendations.scope.types.one
фичи содержат один тип задач.
@ -263,6 +275,33 @@ Bus factor = 1
- документация;
- правки стиля (как результат опроса фокус-группы);
§ recommendations.scope.plan.title
Постройте долгосрочный план
§ recommendations.scope.plan.description
с учетом архитектуры.
При том опираться этот план должен сразу на самые трудные задачи.
# Почему отсутствие плана плохо:
- сотрудники делают минимально работающую версию, не закладывая точки расширения. После этого пишется не масштабируемый код, который тормозит следующие фичи;
# В чём ошибка менеджера:
- он не показал, как продукт будет развиваться далее и в каких точках будет рост;
# Как должно быть:
- составлятся глобальный план развития продукта;
- составлятся глобальный план развития архитектуры (с разработчиками и DBA);
- на уровне схем сразу проговариваются моменты, которые могут сильно измениться;
§ recommendations.scope.cost.title
Оцените инвестиции в фичу
§ recommendations.scope.cost.description
с количеством потенциальной прибыли.
Фичи которые дорого стоят в разработке, но приносят мало прибыли, возможно, стоит отложить или вообще отменить. Это сделает проект более комерчески успешным.
§ recommendations.author.lotOfLazy
пишет слишком мало кода.
@ -344,13 +383,82 @@ Bus factor = 1
- ввести ежедневные совещания, чтобы проверять движение задач по статусу;
- устроить сеансы парного программирования, чтобы убедиться, что разработчик может работать быстрее;
§ recommendations.author.manager.title
Обозначьте дедлайны
§ recommendations.author.manager.description
У любой задачи должен быть чёткий дедлайн.
Это позволит не затягивать её выполнение на несколько дней или недель.
# Какие показатели стоит проверить:
- количество дней на одну задачу, которое тратит работник;
- количество дней ожидания влития PR (страница статистики по PR);
§ recommendations.author.shorTalk.title
Проводите ежедневные совещания
§ recommendations.author.shorTalk.description
они помогают быть в курсе проекта.
Не растягивайте их отвлекаясь на постороние темы.
# На какие вопросы должен ответить сотрудник:
- что было сделано;
- что будет сделано;
- есть ли какие-либо проблемы;
# Следует обрывать монолог, если:
- начинают подробно описывать мелкие детали, которые не важны;
- уводят диалог в сторону, от первоначального плана;
# Почему это важно:
Часто сотрудник, который ничего не делает, старается уйти от ответа. Для этого он рассказывает кучу ненужных подробностей свой работы. Это позволяет усыпить внимание участников и растянуть время ответа. Создается ощущение что он чем-то занят, хотя по факту работы не было.
§ recommendations.author.ipr.title
Составьте план обучения
§ recommendations.author.ipr.description
на каждого сотрудника.
*Индивидуальный план обучения* это список целей и задач, которые помогают человеку развиваться в определенной области.
# Как составить план:
- составить матрицу компетенций;
- определить по каким компетенциям меньше всего знаний и опыта;
- узнать какие из этих компетенций интересны сотруднику;
- придумать 3..5 целей в рамках каждой такой компетенции на пол-года или год;
- каждый месяц пытаться сделать что-либо для достижения одной цели;
- каждый месяц напоминать об общем плане достижения этих целей;
# Нужен ли план руководителю?
Да, руководитель так же должен составить план на себя. Если нет вышестоящего руководителя, то он должен проверять сам себя.
# Почему это важно:
- сотрудники становятся более лояльны к компании;
- за теже деньги вы получаете более квалифицированные кадры;
§ recommendations.author.oneToOne.title
Проводите 1-1 каждый месяц
§ recommendations.author.oneToOne.description
это поможет выявить проблемы на ранней стадии.
*One-to-one* это регулярные личные встречи руководителя с подчиненным. На таких встречах обычно обсуждают всё, что важно для сотрудника, что его волнует, и то, чем он может поделиться с руководителем только наедине.
# Почему это важно:
- легко выяснить, кто из сотрудников перегружен, а у кого есть свободное время;
- можно предотвратить выгорание сотрудника;
- можно получить быструю обратную связь о процессах, которые вы можете не замечать;
- формируется доверительное отношение, сотрудники становятся более лояльны к компании;
- повышается мотивация и вовлеченность сотрудников;
§ recommendations.hour.onlyWork.title
Выходных тут нет
§ recommendations.hour.onlyWork.description
Вероятно, стоит уволить менеджера проекта.
§ recommendations.hour.weekends.title
Работа на выходных
@ -404,13 +512,14 @@ Bus factor = 1
§ recommendations.type.everyHasOne.title
Узкая специализация
Не подписывают тип задачи
§ recommendations.type.everyHasOne.description
большинство типов задач делает один человек.
§ recommendations.type.oneMaintainer.title
Узкая специализация
§ recommendations.type.oneMaintainer.description
большинство задач одного типа делают одни и те же люди.
# Типы задач:
@ -437,6 +546,29 @@ Bus factor = 1
Как это исправить:
- распределять разные типы задач равномерно;
- менять область работы (тесты, документация, ошибки) между сотрудниками через спринт;
§ recommendations.type.fewTypes.title
Это локальный продукт
§ recommendations.type.fewTypes.description
для конкретного заказчика или проблемы.
# Какие признаки есть у «глобального» продукта:
- локализация;
- документация;
- большой обьем тестов;
- визуальная кастомизация;
- рефакторинг узких мест;
- и т.п.
# Почему этот продукт выглядит как «локальный»:
- у каждого «глобального» признака будет перевес по своему типу задач;
- чем больше «глобальных» признаков, тем больше вероятность «глобального» продукта;
В данном случае мы видим небольшое число типов, а следовательно, скорее всего есть недоработки, мешающие легко масштабировать продукт на мировой рынок и продавать его в других странах.
# Возможно, это не так
По типам файлов мы можем предположить тип программы (сайт, серверное приложение, DevOps скрипты и т.д.). Для frontend приложения наша гипотеза будет более верной, чем для DevOps-скриптов, которые могут быть лишь микро-модулем инициализации.
`);
export default {};

View file

@ -0,0 +1,49 @@
import ICommit from 'ts/interfaces/Commit';
import IHashMap from 'ts/interfaces/HashMap';
export default class DataGripByGet {
isGet: IHashMap<boolean> = {};
alreadyAdded: IHashMap<ICommit> = {};
getsByAuthor: IHashMap<ICommit[]> = {};
defaultGets: any = [];
statistic: any = [];
constructor() {
this.createDefaultGets();
this.clear();
}
createDefaultGets() {
const gets = [ '1234', '12345', '123456', '1234567', '12345678' ];
for (let size = 3; size < 7; size++) {
for (let content = 1; content < 9; content++) {
const firstKey = (new Array(size)).fill(content).join('');
gets.push(firstKey);
const lastKey = (new Array(size)).fill('0');
lastKey[0] = content;
gets.push(lastKey.join(''));
}
}
this.defaultGets = gets;
}
clear() {
this.isGet = Object.fromEntries(this.defaultGets.map((key: string) => [key, true]));
this.alreadyAdded = {};
this.getsByAuthor = {};
}
addCommit(commit: ICommit) {
if (!this.isGet[commit.taskNumber]
|| this.alreadyAdded[commit.taskNumber]) return;
this.alreadyAdded[commit.taskNumber] = commit;
this.getsByAuthor[commit.author] = this.getsByAuthor[commit.author] || [];
this.getsByAuthor[commit.author].push(commit);
}
}

37
src/ts/helpers/Math.ts Normal file
View file

@ -0,0 +1,37 @@
import IHashMap from 'ts/interfaces/HashMap';
interface IValueAndCount {
value: number;
count: number;
}
// получить средневзвешенное значение
export class WeightedAverage {
valueAndCount: IHashMap<IValueAndCount> = {};
update(value: number) {
if (this.valueAndCount[value]) {
this.valueAndCount[value].count += 1;
} else {
this.valueAndCount[value] = { value, count: 1 };
}
}
clear() {
this.valueAndCount = {};
}
get() {
let count = 0;
let value = 0;
Object
.values(this.valueAndCount)
.forEach((item: any) => {
value += item.value * item.count;
count += item.count;
});
return value / count;
}
}

View file

@ -0,0 +1,80 @@
const POPULAR_TYPES = [
'refactor',
'feat',
'chore',
'code style',
'style',
'doc',
'docs',
'test',
'update',
'improve',
'add',
'remove',
'delete',
'optimize',
'rename',
'eslint',
'fix',
];
const REPLACE_TYPES = {
add: 'feat',
remove: 'refactor',
delete: 'refactor',
update: 'refactor',
improve: 'refactor',
optimize: 'refactor',
rename: 'refactor',
eslint: 'style',
'code style': 'refactor',
};
function getType(message: string) {
const findType = POPULAR_TYPES.find((item: string) => message.indexOf(item) !== -1);
return REPLACE_TYPES[findType || ''] || findType;
}
function getScope(message: string) {
return message
.replace(/[()]/gim, '')
.split(',')
.map((text: string) => text.trim())
?.[0];
}
export function getTypeAndScope(message: string, task: string) {
let type = '';
let scope = '';
let formattedMessage = message.replace(task, '').toLowerCase();
const messageParts = formattedMessage.split(':');
if (messageParts.length > 1) {
// JIRA-1234 type(scope): message
[type, scope] = messageParts[0].split(/[()]/g).map(v => v.trim());
}
if (!type) {
type = getType(message);
}
if (type && !scope && messageParts.length > 1) {
// JIRA-1234 scope: message
scope = getScope(messageParts[0].replace(type, ''));
}
if (type) { // @ts-ignore
type = type.split(' ').shift();
}
return [type, scope];
}
// ABC-123, #123, gh-123
export function getTask(message: string) {
return ((message || '').match(/(([A-Z]+-)|(#)|(gh-)|(GH-))([0-9]+)/gm) || [])[0] || '';
}
// ABC-123 => '123';
export function getTaskNumber(task?: string) {
return (task || '').replace(/[^0-9]+/gim, '');
}

View file

@ -77,6 +77,26 @@ export default class RecommendationsTeamByAuthor {
`, 'fact']
: null),
// ['Планирование', 'Задачи распределены довольно равномерно', 'info'],
[
localization.get('recommendations.author.manager.title'),
localization.get('recommendations.author.manager.description'),
'info',
],
[
localization.get('recommendations.author.shorTalk.title'),
localization.get('recommendations.author.shorTalk.description'),
'info',
],
[
localization.get('recommendations.author.ipr.title'),
localization.get('recommendations.author.ipr.description'),
'info',
],
[
localization.get('recommendations.author.oneToOne.title'),
localization.get('recommendations.author.oneToOne.description'),
'info',
],
].filter(item => item);
}

View file

@ -9,6 +9,16 @@ export default class RecommendationsTeamByScope {
this.getManyTypes(dataGrip),
this.getParallelism(dataGrip),
[money, localization.get('recommendations.scope.money'), 'fact'],
[
localization.get('recommendations.scope.plan.title'),
localization.get('recommendations.scope.plan.description'),
'info',
],
[
localization.get('recommendations.scope.cost.title'),
localization.get('recommendations.scope.cost.description'),
'info',
],
].filter(item => item);
}

View file

@ -1,4 +1,4 @@
import { getDateLength, getDateByTimestamp } from 'ts/helpers/formatter';
import { getDateByTimestamp } from 'ts/helpers/formatter';
export default class RecommendationsTeamByTimestamp {
getTotalInfo(dataGrip: any) {
@ -7,7 +7,6 @@ export default class RecommendationsTeamByTimestamp {
const byTimestamp = dataGrip.timestamp.statistic;
const workInWeek = byTimestamp.workByDay[5] + byTimestamp.workByDay[6];
const totalDays = byTimestamp.allCommitsByTimestamp.length;
const totalFormattedDays = getDateLength(byTimestamp.allCommitsByTimestamp.length);
// TODO: all days не верный, я вывожу рабочие дни, а не выходные.
return [
@ -18,7 +17,7 @@ export default class RecommendationsTeamByTimestamp {
- сотрудники быстрее выгорают;
`, 'error'] : null,
this.getWorkOnWeek(byTimestamp.allCommitsByTimestamp.length, workInWeek),
[totalFormattedDays, `(или ${totalDays} дней) от первого до последнего коммита (включая выходные и праздники)`, 'fact'],
[`${totalDays} дней работы`, 'от первого до последнего коммита', 'fact'],
this.getFirstDay(byTimestamp),
this.getLastDay(byTimestamp),
].filter(item => item);

View file

@ -2,8 +2,17 @@ import localization from 'ts/helpers/Localization';
export default class RecommendationsTeamByType {
getTotalInfo(dataGrip: any) {
const fewTypes = dataGrip.type.statistic.filter((statistic: any) => (
statistic.tasks > 20
)).length < 7;
return [
this.getBusFactor(dataGrip),
(fewTypes ? [
localization.get('recommendations.type.fewTypes.title'),
localization.get('recommendations.type.fewTypes.description'),
'fact',
] : null),
].filter(item => item);
}

View file

@ -88,14 +88,6 @@ export function getShortNumber(value: number) {
return (value || 0).toFixed(fractionDigits);
}
export function getDateLength(days: number) {
const years = days > 400 ? Math.trunc(days / 365) : 0;
days -= years * 365;
const months = days > 45 ? Math.trunc(days / 30) : 0;
days -= months * 30;
return `${years} года ${months} мес. ${days} дней`;
}
export function getShortName(name: string) {
return name?.split(/[\s.]+/gm)[1] || name;
}

View file

@ -0,0 +1,31 @@
interface IEmploymentContract {
value: number;
currency: string;
workDaysInYear: number;
vacationDaysInYear: number;
workDaysInWeek: number[];
type: string;
}
export interface IEmployeesSalary extends IEmploymentContract {
id: number;
from: string;
milliseconds?: number;
}
export interface IEmployees {
id: number;
name: string;
order: number;
salary: IEmployeesSalary[];
}
export interface IUserSetting {
version: number;
defaultSalary: IEmploymentContract; // TODO: rename defaultEmploymentContract
linksPrefix: {
task: string;
pr: string;
};
employees: IEmployees[];
}

View file

@ -0,0 +1,14 @@
import React from 'react';
function PageBreak() {
return (
<div style={{
pageBreakAfter: 'always',
breakAfter: 'always',
}}>
{' '}
</div>
);
}
export default PageBreak;

View file

@ -8,11 +8,13 @@ import Title from 'ts/components/Title';
interface IPopularWordsProps {
statistic: any[];
mode?: string;
}
function PopularWords({ statistic }: IPopularWordsProps) {
function PopularWords({ statistic, mode }: IPopularWordsProps) {
const limit = mode === 'print' ? 20 : 40;
const dots = statistic
.slice(0, 40)
.slice(0, limit)
.map((titleValue: any) => ({
title: titleValue[0],
value: titleValue[1],
@ -27,7 +29,9 @@ function PopularWords({ statistic }: IPopularWordsProps) {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по словам"/>
<PageWrapper template="table">
<CandyChart

View file

@ -0,0 +1,67 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import UiKitButton from 'ts/components/UiKit/components/Button';
import { Modal, Header, Body } from 'ts/components/ModalWindow';
import style from '../styles/print.module.scss';
import printStore from '../store/Print';
const Print = observer(() => {
if (!printStore.isOpen) return null;
return (
<Modal onClose={() => {
printStore.close();
}}>
<Header>
<div style={{ textAlign: 'center' }}>
Что распечатываем?
</div>
</Header>
<Body>
<img
src="./assets/cards/commits.png"
className={style.page_wrapper_print_icon}
/>
<UiKitButton
className={style.page_wrapper_print_button}
onClick={() => {
printStore.printPage();
}}
>
Текущую страницу
</UiKitButton>
<UiKitButton
className={style.page_wrapper_print_button}
onClick={() => {
printStore.printSection();
}}
>
Текущий раздел
</UiKitButton>
{false && (
<UiKitButton
className={style.page_wrapper_print_button}
onClick={() => {
printStore.printAllPages();
}}
>
Всю статистику
</UiKitButton>
)}
<UiKitButton
type="second"
className={style.page_wrapper_print_button}
onClick={() => {
printStore.close();
}}
>
Отмена
</UiKitButton>
</Body>
</Modal>
);
});
export default Print;

View file

@ -1,5 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
import Buttons from 'ts/pages/Settings/components/Buttons';
@ -8,9 +8,11 @@ import style from '../../styles/header.module.scss';
import Title from './Title';
import Filters from './Filters';
import printStore from '../../store/Print';
const Header = observer((): React.ReactElement | null => {
const navigate = useNavigate();
const location = useLocation();
return (
<header className={style.header}>
@ -21,6 +23,15 @@ const Header = observer((): React.ReactElement | null => {
<>
<Filters/>
<img
title="Печать"
className={style.header_print}
src="./assets/menu/print.svg"
onClick={() => {
printStore.open(navigate, location.pathname);
}}
/>
<img
title="Настройки"
className={style.header_setting}
src="./assets/menu/setting.svg"
onClick={() => {

View file

@ -2,6 +2,7 @@ import React, { ReactNode } from 'react';
import SideBar from './components/sidebar';
import Header from './components/header';
import Print from './components/Print';
import style from './styles/index.module.scss';
interface IPageWrapper {
@ -19,6 +20,7 @@ function PageWrapper({
<div className={style.page_wrapper_main}>
{children}
</div>
<Print />
</div>
);
}

View file

@ -0,0 +1,74 @@
import { makeObservable, observable, action } from 'mobx';
export interface IPrintStore {
open: Function;
close: Function;
}
class PrintStore implements IPrintStore {
isOpen: boolean = false;
navigate: any = null;
prevUrl: string = '';
constructor() {
makeObservable(this, {
isOpen: observable,
open: action,
close: action,
printPage: action,
printSection: action,
printAllPages: action,
triggerPrint: action,
endPrint: action,
});
}
open(navigate: Function, prevUrl: string) {
this.isOpen = true;
this.navigate = navigate;
this.prevUrl = prevUrl;
}
close() {
this.isOpen = false;
this.navigate = null;
this.prevUrl = '';
}
printPage() {
this.triggerPrint();
}
printSection() {
let newUrl: any = this.prevUrl.split('/');
newUrl[2] = 'print';
newUrl = newUrl.join('/');
this.navigate(newUrl);
this.triggerPrint();
}
printAllPages() {
this.triggerPrint();
}
triggerPrint() {
this.isOpen = false;
setTimeout(() => {
window.print();
}, 500);
}
endPrint() {
if (this.prevUrl) this.navigate(this.prevUrl);
this.navigate = null;
this.prevUrl = '';
}
}
const printStore = new PrintStore();
export default printStore;

View file

@ -7,23 +7,33 @@
box-sizing: border-box;
text-align: right;
background-color: #FFFFFF;
&_title {
float: left;
font-size: 24px;
font-weight: 100;
display: inline-block;
margin: 0;
color: var(--color-black);
}
&_print,
&_setting {
display: inline-block;
width: 24px;
height: 24px;
padding: 0;
cursor: pointer;
user-select: none;
vertical-align: top;
}
&_print {
margin-right: 24px;
}
}
.header_title {
float: left;
font-size: 24px;
font-weight: 100;
display: inline-block;
margin: 0;
color: var(--color-black);
}
.header_setting {
display: inline-block;
width: 24px;
height: 24px;
padding: 0;
cursor: pointer;
user-select: none;
vertical-align: top;
@media print {
.header {
display: none;
}
}

View file

@ -16,6 +16,7 @@
min-height: calc(100vh - 72px);
padding: 24px;
box-sizing: border-box;
break-inside: auto;
}
}
@ -28,3 +29,14 @@
}
}
}
@media print {
.page_wrapper {
grid-template-areas: 'main main' 'main main';
&_main {
width: 100vw;
padding: 12px 0;
}
}
}

View file

@ -0,0 +1,26 @@
@import '../../../../styles/variables';
.page_wrapper_print {
&_icon {
display: block;
width: 70%;
margin: 24px auto;
}
&_button {
display: block;
width: 99%;
margin: 12px auto;
&:first-child {
margin-top: 24px;
}
&:last-child {
margin-bottom: 24px;
}
}
}
.page_wrapper_print_button + .page_wrapper_print_button {
margin-right: auto;
}

View file

@ -83,4 +83,10 @@
.sidebar_item_icon {
margin: 0;
}
}
@media print {
.sidebar {
display: none;
}
}

View file

@ -0,0 +1,28 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
import dataGripStore from 'ts/store/DataGrip';
import YearChart from 'ts/components/YearChart';
import PageWrapper from 'ts/components/Page/wrapper';
const Month = observer((): React.ReactElement => {
const { userId } = useParams<any>();
const author = dataGripStore.dataGrip.author.statistic[userId || 0];
const statistic = dataGripStore.dataGrip.timestamp.statisticByAuthor[author.author];
const max = statistic.commitsByTimestampCounter.max;
return (
<PageWrapper template="table">
<YearChart
showEvents={false}
maxCommits={max}
authors={[author]}
wordDays={statistic.allCommitsByTimestamp}
/>
</PageWrapper>
);
});
export default Month;

View file

@ -4,12 +4,18 @@ import { observer } from 'mobx-react-lite';
import dataGripStore from 'ts/store/DataGrip';
import CommonPopularWords from 'ts/pages/Common/components/PopularWords';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
const PopularWords = observer((): React.ReactElement => {
const PopularWords = observer(({
mode,
}: ICommonPageProps): React.ReactElement => {
const { userId } = useParams<any>();
const statistic = dataGripStore.dataGrip.author.statistic[userId || 0].wordStatistics;
return (
<CommonPopularWords statistic={statistic} />
<CommonPopularWords
mode={mode}
statistic={statistic}
/>
);
});

View file

@ -0,0 +1,34 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import PageBreak from 'ts/pages/Common/components/PageBreak';
import Hours from './Hours';
import Money from './Money';
import PopularWords from './PopularWords';
import Speed from './Speed';
import Total from './print/Total';
import Achievements from './print/Achievements';
import Week from './Week';
import Month from './Month';
const Print = observer((): React.ReactElement => {
return (
<>
<Total/>
<Speed/>
<Money/>
<PageBreak/>
<Achievements/>
<PageBreak/>
<Hours/>
<Week mode="print"/>
<PageBreak/>
<Month/>
<Hours/>
<PopularWords mode="print"/>
</>
);
});
export default Print;

View file

@ -92,7 +92,7 @@ const Tempo = observer((): React.ReactElement => {
<PageWrapper template="table">
<DataLoader
to="response"
loader={() => getFakeLoader(partOfData)}
loader={() => getFakeLoader({ content: partOfData })}
watch={week}
>
<TempoView

View file

@ -46,7 +46,7 @@ const Total = observer((): React.ReactElement => {
<CardWithIcon
value={statistic.daysWorked}
icon="./assets/cards/work_days.png"
title="page.team.total.daysWorked.title"
title="дней работы"
description="page.team.total.daysWorked.description"
/>
<CardWithIcon

View file

@ -9,10 +9,9 @@ import dataGripStore from 'ts/store/DataGrip';
import PageWrapper from 'ts/components/Page/wrapper';
import DataLoader from 'ts/components/DataLoader';
import LoadMore from 'ts/components/DataLoader/components/LoadMore';
import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title';
import Table from 'ts/components/Table';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
@ -21,6 +20,8 @@ import getOptions from 'ts/components/LineChart/helpers/getOptions';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import ISort from 'ts/interfaces/Sort';
interface IWeekViewProps {
name: string;
@ -105,7 +106,9 @@ WeekView.defaultProps = {
response: undefined,
};
const Week = observer((): React.ReactElement => {
const Week = observer(({
mode,
}: ICommonPageProps): React.ReactElement => {
const { userId } = useParams<any>();
const statistic = dataGripStore.dataGrip.author.statistic[userId || 0];
const rows = dataGripStore.dataGrip.week.statistic.filter((item: any) => item.authors[statistic.author]);
@ -114,15 +117,18 @@ const Week = observer((): React.ReactElement => {
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Title title="Статистика по неделям"/>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest) => getFakeLoader(rows, pagination)}
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort,
})}
>
<WeekView name={statistic.author} />
<LoadMore />
{mode !== 'print' && <Pagination />}
</DataLoader>
</PageWrapper>
</>

View file

@ -0,0 +1,75 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
import getAchievementByAuthor from 'ts/helpers/achievement/byAuthor';
import ACHIEVEMENT_TYPE from 'ts/helpers/achievement/constants/type';
import localization from 'ts/helpers/Localization';
import Achievements from 'ts/components/Achievement';
import Description from 'ts/components/Description';
import PageWrapper from 'ts/components/Page/wrapper';
import PageColumn from 'ts/components/Page/column';
import Title from 'ts/components/Title';
import GetList from 'ts/components/GetList';
import dataGripStore from 'ts/store/DataGrip';
interface IAchievementBlockProps {
title: string;
achievements: string[];
}
function AchievementBlock({ title, achievements }: IAchievementBlockProps) {
if (!achievements.length) return null;
return (
<>
<Description text={`# ${title}`}/>
<Achievements list={achievements} />
</>
);
}
const Total = observer((): React.ReactElement => {
const { userId } = useParams<any>();
const statistic = dataGripStore.dataGrip.author.statistic[userId || 0];
const commitsWithGet = dataGripStore.dataGrip.get.getsByAuthor[statistic.author];
const achievements = getAchievementByAuthor(statistic.author);
return (
<PageWrapper>
<PageColumn>
<Title title={localization.get('Достижения')}/>
<AchievementBlock
title="Позитивные"
achievements={achievements[ACHIEVEMENT_TYPE.GOOD]}
/>
</PageColumn>
<PageColumn>
<Title title={localization.get('_')}/>
<AchievementBlock
title="Нейтральные"
achievements={achievements[ACHIEVEMENT_TYPE.NORMAL]}
/>
<AchievementBlock
title="Негативные"
achievements={achievements[ACHIEVEMENT_TYPE.BAD]}
/>
<br />
<br />
{commitsWithGet?.length ? (
<>
<Title title={localization.get('Взятые геты:')}/>
<GetList
mode="print"
list={commitsWithGet}
/>
<Description text="&laquo;Взять гет&raquo; в данном случае означает первым оставить коммит к&nbsp;задаче с&nbsp;&laquo;красивым&raquo; номером."/>
</>
) : null}
</PageColumn>
</PageWrapper>
);
});
export default Total;

View file

@ -0,0 +1,63 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
import CardWithIcon from 'ts/components/CardWithIcon';
import PageWrapper from 'ts/components/Page/wrapper';
import PageColumn from 'ts/components/Page/column';
import Title from 'ts/components/Title';
import dataGripStore from 'ts/store/DataGrip';
import style from '../../styles/print.module.scss';
const Total = observer((): React.ReactElement => {
const { userId } = useParams<any>();
const statistic = dataGripStore.dataGrip.author.statistic[userId || 0];
const taskNumber = statistic.tasks.length;
return (
<PageWrapper>
<PageColumn>
<CardWithIcon
value=""
icon="./assets/cards/work_days.png"
title="Фотограция"
/>
<div className={style.place_for_photo}>
место для фотографии
</div>
</PageColumn>
<PageColumn>
<Title title={statistic.author} />
<div>
<CardWithIcon
value={statistic.daysWorked}
icon="./assets/cards/work_days.png"
title="дней работы"
description="page.team.total.daysWorked.description"
/>
<CardWithIcon
value={taskNumber ? taskNumber : null}
icon="./assets/cards/tasks.png"
title="задач"
description="Если коммиты правильно подписаны"
/>
<CardWithIcon
value={statistic.daysLosses}
icon="./assets/cards/lazy.png"
title="page.team.total.daysLosses.title"
description="page.team.total.daysLosses.description"
/>
<CardWithIcon
value={statistic.commits}
icon="./assets/cards/commits.png"
title="page.team.total.commits.title"
description="page.team.total.commits.description"
/>
</div>
</PageColumn>
</PageWrapper>
);
});
export default Total;

View file

@ -15,13 +15,14 @@ import Total from './components/Total';
import Week from './components/Week';
import Month from './components/Month';
import Tempo from './components/Tempo';
import Print from './components/Print';
function Person() {
const { type, page } = useParams<any>();
if (type !== 'person') return null;
return (
<>
{page !== 'week' && (
{!['week', 'print'].includes(page || '') && (
<>
<Title title={localization.get('common.filters')} />
<UserSelect />
@ -37,6 +38,7 @@ function Person() {
{page === 'words' && <PopularWords/>}
{page === 'speed' && <Speed/>}
{page === 'day' && <Tempo/>}
{page === 'print' && <Print/>}
</>
);
}

View file

@ -0,0 +1,24 @@
@import '../../../../styles/variables';
.place_for_photo {
font-weight: 100;
font-size: var(--font-xs);
display: block;
width: 60%;
min-height: 232px;
margin: 44px auto;
padding: 0;
line-height: 232px;
vertical-align: top;
text-decoration: none;
box-sizing: border-box;
text-align: center;
border-radius: 8px;
border: 1px solid var(--color-border);
background-color: #FFFFFF;
color: var(--color-grey);
}

View file

@ -7,6 +7,7 @@ import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination';
import { getMoney, getShortNumber } from 'ts/helpers/formatter';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import PageWrapper from 'ts/components/Page/wrapper';
import PageColumn from 'ts/components/Page/column';
import DataLoader from 'ts/components/DataLoader';
@ -55,7 +56,7 @@ function AuthorView({ response, updateSort }: IAuthorViewProps) {
<Column
isSortable="daysWorked"
title="page.team.author.workedLosses"
width={400}
minWidth={300}
template={(details: any) => (
<LineChart
options={daysWorked}
@ -75,6 +76,7 @@ function AuthorView({ response, updateSort }: IAuthorViewProps) {
isSortable
properties="tasks"
title="page.team.author.tasks"
minWidth={200}
template={(value: number) => (
<LineChart
options={taskChart}
@ -103,6 +105,7 @@ function AuthorView({ response, updateSort }: IAuthorViewProps) {
isSortable
title="page.team.author.commits"
properties="commits"
minWidth={100}
template={(value: number) => (
<LineChart
options={commitsChart}
@ -147,19 +150,25 @@ AuthorView.defaultProps = {
response: undefined,
};
const Author = observer((): React.ReactElement => {
const Author = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.author.statistic;
if (!rows?.length) return (<NothingFound />);
if (!rows?.length) return mode !== 'print' ? (<NothingFound />) : null;
const recommendations = dataGripStore.dataGrip.recommendations.team?.byAuthor;
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
<Title title="Статистика по фичам"/>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по сотрудникам"/>
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader(rows, pagination, '', sort)}
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows, pagination, sort, mode,
})}
>
<AuthorView />
<Pagination />

View file

@ -0,0 +1,39 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import localization from 'ts/helpers/Localization';
import dataGripStore from 'ts/store/DataGrip';
import RecommendationsWrapper from 'ts/components/Recommendations/wrapper';
import YearChart from 'ts/components/YearChart';
import Title from 'ts/components/Title';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import PageWrapper from 'ts/components/Page/wrapper';
const Month = observer(({
mode,
}: ICommonPageProps): React.ReactElement => {
const authors = dataGripStore.dataGrip.author.statistic;
const statistic = dataGripStore.dataGrip.timestamp.statistic;
const max = statistic.commitsByTimestampCounter.max;
const recommendations = dataGripStore.dataGrip.recommendations.team?.byTimestamp;
return (
<>
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations}/>
)}
<Title title={localization.get('Календарь работы по проекту')}/>
<PageWrapper template="table">
<YearChart
maxCommits={max}
authors={authors}
wordDays={statistic.allCommitsByTimestamp}
/>
</PageWrapper>
</>
);
});
export default Month;

View file

@ -0,0 +1,163 @@
import React from 'react';
import { IPagination } from 'ts/interfaces/Pagination';
import dataGripStore from 'ts/store/DataGrip';
import userSettings from 'ts/store/UserSettings';
import Table from 'ts/components/Table';
import Column from 'ts/components/Table/components/Column';
import { ColumnTypesEnum } from 'ts/components/Table/interfaces/Column';
import ExternalLink from 'ts/components/ExternalLink';
import LineChart from 'ts/components/LineChart';
import getOptions from 'ts/components/LineChart/helpers/getOptions';
import { getMax } from 'ts/pages/Common/helpers/getMax';
import { getDate } from 'ts/helpers/formatter';
interface IPRViewProps {
mode?: string;
response?: IPagination<any>;
updateSort?: Function;
}
function AllPR({
mode,
response,
updateSort,
}: IPRViewProps) {
if (!response) return null;
const workChart = getOptions({ max: getMax(response, 'workDays') });
const delayChart = getOptions({ max: getMax(response, 'delayDays') });
const commitsChart = getOptions({
max: getMax(response, 'commits'),
order: dataGripStore.dataGrip.author.list,
});
return (
<Table
rows={response.content}
sort={response.sort}
updateSort={updateSort}
>
{mode === 'print' ? (
<Column
isSortable
title="Задача"
properties="task"
width={120}
/>
) : (
<Column
isSortable
template={(value: string, row: any) => {
return (
<>
<ExternalLink
link={`${userSettings?.settings?.linksPrefix?.task || '/'}${value}`}
text={value}
/>
<ExternalLink
link={`${userSettings?.settings?.linksPrefix?.pr || '/'}${row?.prId}`}
text="PR"
/>
</>
);
}}
title="Задача"
properties="task"
width={120}
/>
)}
<Column
isSortable
template={ColumnTypesEnum.STRING}
title="Первый коммит"
properties="beginTaskTime"
formatter={getDate}
width={130}
/>
<Column
isSortable
template={ColumnTypesEnum.STRING}
title="Последний"
properties="endTaskTime"
formatter={getDate}
width={130}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="workDays"
width={40}
/>
<Column
isSortable
title="Дней в работе"
properties="workDays"
minWidth={100}
template={(value: any) => (
<LineChart
options={workChart}
value={value}
/>
)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="commits"
width={40}
/>
<Column
isSortable
title="Коммиты"
properties="commitsByAuthors"
minWidth={100}
template={(details: any) => (
<LineChart
options={commitsChart}
details={details}
/>
)}
/>
<Column
template={ColumnTypesEnum.SHORT_NUMBER}
properties="delayDays"
width={40}
/>
<Column
isSortable
title="Дней ожидания влития"
properties="delayDays"
minWidth={200}
template={(value: any) => (
<LineChart
options={delayChart}
value={value}
/>
)}
/>
<Column
isSortable
template={ColumnTypesEnum.STRING}
title="Дата влития"
properties="milliseconds"
formatter={getDate}
width={130}
/>
<Column
isSortable
template={ColumnTypesEnum.STRING}
title="Влил"
properties="author"
width={250}
/>
</Table>
);
}
AllPR.defaultProps = {
mode: undefined,
response: undefined,
};
export default AllPR;

View file

@ -0,0 +1,97 @@
import React from 'react';
import { IPagination } from 'ts/interfaces/Pagination';
import Table from 'ts/components/Table';
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 { getMax } from 'ts/pages/Common/helpers/getMax';
const TITLES = {
DAY: 'день',
THREE_DAY: 'три дня',
WEEK: 'неделя',
TWO_WEEK: 'две недели',
MONTH: 'месяц',
MORE: 'более',
};
const order = Object.values(TITLES);
interface IAuthorsProps {
response?: IPagination<any>;
updateSort?: Function;
}
function Authors({ response, updateSort }: IAuthorsProps) {
if (!response) return null;
const timeChart = getOptions({ order, limit: 3 });
const weightedAverageChart = getOptions({
max: getMax(response, 'weightedAverage'),
order: ['разработка', 'ожидание'],
suffix: 'дней',
});
return (
<Table
rows={response.content}
sort={response.sort}
updateSort={updateSort}
>
<Column
isSortable
template={ColumnTypesEnum.STRING}
title="Сотрудник"
properties="author"
width={250}
/>
<Column
title="Время разработки"
properties="workDays"
template={(details: any) => (
<LineChart
options={timeChart}
details={details}
/>
)}
/>
<Column
title="Время ожидания влития"
properties="delayDays"
template={(details: any) => (
<LineChart
options={timeChart}
details={details}
/>
)}
/>
<Column
properties="weightedAverage"
template={ColumnTypesEnum.SHORT_NUMBER}
/>
<Column
title="Среднее время поставки (дни)"
properties="weightedAverageDetails"
width={300}
template={(item: any, row: any) => (
<LineChart
options={weightedAverageChart}
value={row.weightedAverage}
details={{
'разработка': item.workDays,
'ожидание': item.delayDays,
}}
/>
)}
/>
</Table>
);
}
Authors.defaultProps = {
response: undefined,
};
export default Authors;

View file

@ -0,0 +1,86 @@
import React from 'react';
import Table from 'ts/components/Table';
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 dataGripStore from 'ts/store/DataGrip';
import DataGripByPR from 'ts/helpers/DataGrip/components/pr';
function Total() {
const allPR = dataGripStore.dataGrip.pr.statistic;
const workChart = DataGripByPR.getPRByGroups(allPR, 'workDays');
const workChartOptions = getOptions({ order: workChart.order, limit: 3, suffix: 'задачь' });
const delayChart = DataGripByPR.getPRByGroups(allPR, 'delayDays');
const delayChartOptions = getOptions({ order: delayChart.order, limit: 3, suffix: 'PR' });
const workDaysWeightedAverage = Math.round(workChart.weightedAverage);
const delayDaysWeightedAverage = Math.round(delayChart.weightedAverage);
const weightedAverage = workDaysWeightedAverage + delayDaysWeightedAverage;
const weightedAverageChart = getOptions({ // @ts-ignore
order: ['разработка', 'ожидание'],
suffix: 'дней',
});
const rows = [
{
workDays: workChart.details,
delayDays: delayChart.details,
weightedAverage: weightedAverage.toFixed(1),
weightedAverageDetails: {
workDays: workDaysWeightedAverage,
delayDays: delayDaysWeightedAverage,
},
},
];
return (
<Table rows={rows}>
<Column
title="Время разработки"
properties="workDays"
template={(details: any) => (
<LineChart
options={workChartOptions}
details={details}
/>
)}
/>
<Column
title="Время ожидания влития"
properties="delayDays"
template={(details: any) => (
<LineChart
options={delayChartOptions}
details={details}
/>
)}
/>
<Column
properties="weightedAverage"
template={ColumnTypesEnum.SHORT_NUMBER}
/>
<Column
title="Среднее время поставки (дни)"
properties="weightedAverageDetails"
width={300}
template={(item: any) => (
<LineChart
options={weightedAverageChart}
details={{
'разработка': item.workDays,
'ожидание': item.delayDays,
}}
/>
)}
/>
</Table>
);
}
export default Total;

View file

@ -0,0 +1,92 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import ISort from 'ts/interfaces/Sort';
import { IPaginationRequest } from 'ts/interfaces/Pagination';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import PageWrapper from 'ts/components/Page/wrapper';
import PageColumn from 'ts/components/Page/column';
import Description from 'ts/components/Description';
import DataLoader from 'ts/components/DataLoader';
import Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import Title from 'ts/components/Title';
import PageBreak from 'ts/pages/Common/components/PageBreak';
import Total from './Total';
import Authors from './Authors';
import All from './All';
const PR = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const allPR = dataGripStore.dataGrip.pr.statistic;
const rows = allPR.filter((item: any) => item.delayDays > 3);
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
const PRbyName = dataGripStore.dataGrip.pr.statisticByName;
const authorsStat = Object.values(PRbyName);
return (
<>
<Title title="Время потраченное на одну задачу"/>
<PageWrapper template="table">
<Total/>
</PageWrapper>
<PageWrapper>
<PageColumn>
<Description
text="*Время разработки* это разница времени от первого до последнего коммита по задаче. Не важно были перерывы в несколько дней между коммитами или нет. Сам факт какого-либо коммита увеличивает время."
/>
<Description
text="*Время ожидания* это время между последним коммитом и влитием кода. Оно показывает фактический простой в ожидании чего-либо."
/>
</PageColumn>
<PageColumn>
<Description
text="*Зачем отображать время разработки* без разбивки на кодинг и код-ревью? Затем, чтобы показать бизнесу фактическое время поставки кода. Ожидание тестирования, замечания на ревью, проблемы DevOps и прочие несовершенства процесса, как раз уже заложены в этот срок."
/>
</PageColumn>
</PageWrapper>
<br/>
<br/>
<Title title="Статистика по сотрудникам"/>
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: authorsStat, pagination, sort, mode,
})}
>
<Authors/>
<Pagination/>
</DataLoader>
</PageWrapper>
<PageBreak/>
<Title title="Длительное ожидание влития"/>
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest, sort?: ISort[]) => getFakeLoader({
content: rows,
pagination: mode === 'print'
? { size: 20 }
: pagination,
sort,
})}
>
<All mode={mode} />
{mode !== 'print' && <Pagination/>}
</DataLoader>
</PageWrapper>
</>
);
});
export default PR;

View file

@ -0,0 +1,38 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import PageBreak from 'ts/pages/Common/components/PageBreak';
import Author from './Author';
import Hours from './Hours';
import PopularWords from './PopularWords';
import Scope from './Scope';
import Total from './Total';
import Type from './Type';
import Week from './Week';
import Month from './Month';
import Pr from './PR';
const Print = observer((): React.ReactElement => {
return (
<>
<Total/>
<PageBreak/>
<Scope mode="print"/>
<PageBreak/>
<Author mode="print"/>
<PageBreak/>
<Type mode="print"/>
<PageBreak/>
<Pr mode="print"/>
<PageBreak/>
<Week mode="print"/>
<PageBreak/>
<Month mode="print"/>
<Hours/>
<PopularWords/>
</>
);
});
export default Print;

View file

@ -5,6 +5,7 @@ import { IPaginationRequest, IPagination } from 'ts/interfaces/Pagination';
import { getMoney } from 'ts/helpers/formatter';
import dataGripStore from 'ts/store/DataGrip';
import ICommonPageProps from 'ts/components/Page/interfaces/CommonPageProps';
import PageWrapper from 'ts/components/Page/wrapper';
import DataLoader from 'ts/components/DataLoader';
import Pagination from 'ts/components/DataLoader/components/Pagination';
@ -65,7 +66,7 @@ function ScopeView({ response }: IScopeViewProps) {
<Column
title="page.team.scope.types"
properties="types" // TODO: нужно по числу изменений, а не коммитов
width={200}
minWidth={200}
template={(details: any) => (
<LineChart
options={typeChart}
@ -76,7 +77,7 @@ function ScopeView({ response }: IScopeViewProps) {
<Column
title="page.team.scope.authors"
properties="authors"
width={200}
minWidth={200}
formatter={(authors: any) => {
return Object.fromEntries(
Object.keys(authors).map(name => [name, authors[name]?.commits || 0]),
@ -103,19 +104,25 @@ ScopeView.defaultProps = {
response: undefined,
};
const Scope = observer((): React.ReactElement => {
const Scope = observer(({
mode,
}: ICommonPageProps): React.ReactElement | null => {
const rows = dataGripStore.dataGrip.scope.statistic;
if (rows?.length < 2) return (<NothingFound />);
if (rows?.length < 2) return mode !== 'print' ? (<NothingFound />) : null;
const recommendations = dataGripStore.dataGrip.recommendations.team?.byScope;
return (
<>
<RecommendationsWrapper recommendations={recommendations} />
{mode !== 'print' && (
<RecommendationsWrapper recommendations={recommendations} />
)}
<Title title="Статистика по фичам"/>
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest) => getFakeLoader(rows, pagination)}
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: rows, pagination, mode,
})}
>
<ScopeView />
<Pagination />

View file

@ -10,7 +10,6 @@ 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 Pagination from 'ts/components/DataLoader/components/Pagination';
import getFakeLoader from 'ts/components/DataLoader/helpers/formatter';
import NothingFound from 'ts/components/NothingFound';
import TempoChart from 'ts/components/Tempo';
@ -118,14 +117,13 @@ const Tempo = observer((): React.ReactElement => {
<PageWrapper template="table">
<DataLoader
to="response"
loader={() => getFakeLoader(partOfData)}
loader={() => getFakeLoader({ content: partOfData })}
watch={`${week}${user}`}
>
<TempoView
order={order}
user={user}
/>
<Pagination />
</DataLoader>
</PageWrapper>
</>

View file

@ -55,7 +55,7 @@ function TreeView({ response }: ITreeViewProps) {
isFixed
template={ColumnTypesEnum.STRING}
properties="title"
width={200}
minWidth={200}
onClick={(row: any) => {
treeStore.updateFilter('selectedPath', row.path || []);
}}
@ -63,7 +63,7 @@ function TreeView({ response }: ITreeViewProps) {
<Column
title="Процент перезаписи строк"
properties="file"
width={250}
minWidth={250}
template={(file: any) => (
<LineChart
value={file ? 100 : 0}
@ -78,7 +78,7 @@ function TreeView({ response }: ITreeViewProps) {
<Column
title="Кто добавлял"
properties="file"
width={200}
minWidth={200}
template={(file: any) => (
<LineChart
value={file?.total?.added ? 100 : 0}
@ -90,7 +90,7 @@ function TreeView({ response }: ITreeViewProps) {
<Column
title="Кто менял"
properties="file"
width={200}
minWidth={200}
template={(file: any) => (
<LineChart
value={file?.total?.changes ? 100 : 0}
@ -102,7 +102,7 @@ function TreeView({ response }: ITreeViewProps) {
<Column
title="Кто удалял"
properties="file"
width={200}
minWidth={200}
template={(file: any) => (
<LineChart
value={file?.total?.removed ? 100 : 0}
@ -132,7 +132,9 @@ const Tree = observer((): React.ReactElement => {
<PageWrapper template="table">
<DataLoader
to="response"
loader={(pagination?: IPaginationRequest) => getFakeLoader(fileList, { ...pagination, size: 500 })}
loader={(pagination?: IPaginationRequest) => getFakeLoader({
content: fileList, pagination: { ...pagination, size: 500 },
})}
watch={treeStore.hash}
>
<TreeView />

Some files were not shown because too many files have changed in this diff Show more