JIRA-1234 test(doc): update some text
3
Dockerfile
Normal file
|
@ -0,0 +1,3 @@
|
|||
FROM nginx
|
||||
COPY build /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
|
@ -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"
|
||||
]
|
||||
}
|
1
build/assets/achievements/adam.svg
Normal 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 |
1
build/assets/achievements/more666DaysInProject.svg
Normal 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 |
1
build/assets/achievements/more777DaysInProject.svg
Normal 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 |
1
build/assets/menu/print.svg
Normal 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 |
3
build/assets/menu/pull_request.svg
Normal 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
|
@ -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 |
|
@ -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>
|
|
@ -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"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
2
build/static/css/main.48701252.css
Normal file
1
build/static/css/main.48701252.css.map
Normal file
3
build/static/js/main.d0ec1697.js
Normal file
74
build/static/js/main.d0ec1697.js.LICENSE.txt
Normal 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
|
||||
*/
|
1
build/static/js/main.d0ec1697.js.map
Normal file
46
nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "my",
|
||||
"name": "Assayo",
|
||||
"version": "0.1.0",
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
|
|
1
public/assets/achievements/adam.svg
Normal 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 |
1
public/assets/achievements/more666DaysInProject.svg
Normal 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 |
1
public/assets/achievements/more777DaysInProject.svg
Normal 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 |
1
public/assets/menu/print.svg
Normal 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 |
3
public/assets/menu/pull_request.svg
Normal 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
|
@ -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 |
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
1
src/assets/menu/print.svg
Normal 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 |
3
src/assets/menu/pull_request.svg
Normal 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 |
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
26
src/ts/components/ExternalLink/index.module.scss
Normal 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;
|
||||
}
|
||||
}
|
23
src/ts/components/ExternalLink/index.tsx
Normal 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;
|
43
src/ts/components/GetList/components/Item.tsx
Normal 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;
|
29
src/ts/components/GetList/index.tsx
Normal 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;
|
77
src/ts/components/GetList/styles/index.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
text-align: center;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #FFFFFF;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
&_work {
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
text-align: left;
|
||||
color: #FFFFFF;
|
||||
background-color: #D0D1D2;
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
.line_chart_sub_item {
|
||||
|
|
|
@ -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 || ''}`}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
3
src/ts/components/Page/interfaces/CommonPageProps.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default interface ICommonPageProps {
|
||||
mode?: string;
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) => ({
|
||||
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 || defaultWidth || column.width || 150,
|
||||
}));
|
||||
width: column.userWidth || column.defaultWidth || adaptiveColumnWidth, // || column.width || 150,
|
||||
};
|
||||
});
|
||||
|
||||
const middle = Math.floor(columns.length / 2);
|
||||
return [
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -38,6 +38,8 @@ export interface IColumn {
|
|||
className?: string | Function
|
||||
/** Стилья для колонки */
|
||||
style?: Function,
|
||||
/** Минимальная ширина столбца если он адаптивен */
|
||||
minWidth?: number,
|
||||
/** Ширина столбца заданная в верстке */
|
||||
defaultWidth?: number,
|
||||
/** Ширина столбца установленная пользователем */
|
||||
|
|
|
@ -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 {
|
||||
|
@ -90,3 +92,16 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
56
src/ts/components/UiKit/components/SelectWithButtons.tsx
Normal 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;
|
|
@ -116,7 +116,7 @@
|
|||
}
|
||||
|
||||
.button + .button {
|
||||
margin: 0 0 0 24px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.ui_kit_select {
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
{!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;
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
39
src/ts/components/YearChart/styles/line.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -171,6 +171,15 @@ git может показать малое количество изменени
|
|||
- находим среднее арифметическое;
|
||||
- если результат меньше 1.3 считаем, что паралельных работ в рамках большинства фичей обычно нет;
|
||||
|
||||
# Почему это плохо:
|
||||
- повышается bus factor;
|
||||
- сотрудники медленее развиваются;
|
||||
- трудно качественно проверить работу сотрудника;
|
||||
|
||||
# Почему это хорошо:
|
||||
- появляюся эксперты, которые очень глубоко погружены в предметную область и могут предложить более качественные решения;
|
||||
- скорее всего не бывает merge конфликтов;
|
||||
- проект может очень быстро паралельно развиваться в разные стороны;
|
||||
|
||||
§ recommendations.scope.parallelism.has.title
|
||||
Часть работ паралельно
|
||||
|
@ -204,6 +213,12 @@ 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 {};
|
||||
|
|
49
src/ts/helpers/DataGrip/components/get.ts
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
80
src/ts/helpers/Parser/getTypeAndScope.ts
Normal 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, '');
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
31
src/ts/interfaces/UserSetting.ts
Normal 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[];
|
||||
}
|
14
src/ts/pages/Common/components/PageBreak.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
function PageBreak() {
|
||||
return (
|
||||
<div style={{
|
||||
pageBreakAfter: 'always',
|
||||
breakAfter: 'always',
|
||||
}}>
|
||||
{' '}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageBreak;
|
|
@ -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 (
|
||||
<>
|
||||
{mode !== 'print' && (
|
||||
<RecommendationsWrapper recommendations={recommendations} />
|
||||
)}
|
||||
<Title title="Статистика по словам"/>
|
||||
<PageWrapper template="table">
|
||||
<CandyChart
|
||||
|
|
67
src/ts/pages/PageWrapper/components/Print.tsx
Normal 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;
|
|
@ -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={() => {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
74
src/ts/pages/PageWrapper/store/Print.tsx
Normal 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;
|
|
@ -7,18 +7,18 @@
|
|||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.header_title {
|
||||
&_title {
|
||||
float: left;
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
color: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.header_setting {
|
||||
&_print,
|
||||
&_setting {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
@ -26,4 +26,14 @@
|
|||
cursor: pointer;
|
||||
user-select: none;
|
||||
vertical-align: top;
|
||||
}
|
||||
&_print {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
src/ts/pages/PageWrapper/styles/print.module.scss
Normal 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;
|
||||
}
|
|
@ -84,3 +84,9 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
28
src/ts/pages/Person/components/Month.tsx
Normal 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;
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
34
src/ts/pages/Person/components/Print.tsx
Normal 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;
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
{mode !== 'print' && (
|
||||
<RecommendationsWrapper recommendations={recommendations} />
|
||||
<Title title="Статистика по неделям"/>
|
||||
)}
|
||||
<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>
|
||||
</>
|
||||
|
|
75
src/ts/pages/Person/components/print/Achievements.tsx
Normal 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="«Взять гет» в данном случае означает первым оставить коммит к задаче с «красивым» номером."/>
|
||||
</>
|
||||
) : null}
|
||||
</PageColumn>
|
||||
</PageWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
export default Total;
|
63
src/ts/pages/Person/components/print/Total.tsx
Normal 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;
|
|
@ -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/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
24
src/ts/pages/Person/styles/print.module.scss
Normal 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);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
{mode !== 'print' && (
|
||||
<RecommendationsWrapper recommendations={recommendations} />
|
||||
<Title title="Статистика по фичам"/>
|
||||
)}
|
||||
<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 />
|
||||
|
|
39
src/ts/pages/Team/components/Month.tsx
Normal 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;
|
163
src/ts/pages/Team/components/PR/All.tsx
Normal 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;
|
97
src/ts/pages/Team/components/PR/Authors.tsx
Normal 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;
|
86
src/ts/pages/Team/components/PR/Total.tsx
Normal 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;
|
92
src/ts/pages/Team/components/PR/index.tsx
Normal 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;
|
38
src/ts/pages/Team/components/Print.tsx
Normal 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;
|
|
@ -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 (
|
||||
<>
|
||||
{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 />
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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 />
|
||||
|
|