JIRA-1234 feat(mobile): some text

This commit is contained in:
Бахирев 2024-02-13 01:02:08 +03:00
parent 81f81a86dd
commit d8cf1d6efe
40 changed files with 662 additions and 129 deletions

View file

@ -1,7 +1,7 @@
{ {
"files": { "files": {
"main.css": "./static/css/main.3a144124.css", "main.css": "./static/css/main.1c4d936b.css",
"main.js": "./static/js/main.1f52d26d.js", "main.js": "./static/js/main.13141a1e.js",
"static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png", "static/media/car.png": "./static/media/car.b8dd8738e37fe866285f.png",
"index.html": "./index.html", "index.html": "./index.html",
"static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg", "static/media/warning.svg": "./static/media/warning.e39a87773603f3ab157f.svg",
@ -9,11 +9,11 @@
"static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg", "static/media/alert.svg": "./static/media/alert.41e2b99c481139c13074.svg",
"static/media/arrow_left.svg": "./static/media/arrow_left.d053cbdc58069cfc01de.svg", "static/media/arrow_left.svg": "./static/media/arrow_left.d053cbdc58069cfc01de.svg",
"static/media/arrow_right.svg": "./static/media/arrow_right.7caaf9eb44d9210be019.svg", "static/media/arrow_right.svg": "./static/media/arrow_right.7caaf9eb44d9210be019.svg",
"main.3a144124.css.map": "./static/css/main.3a144124.css.map", "main.1c4d936b.css.map": "./static/css/main.1c4d936b.css.map",
"main.1f52d26d.js.map": "./static/js/main.1f52d26d.js.map" "main.13141a1e.js.map": "./static/js/main.13141a1e.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.3a144124.css", "static/css/main.1c4d936b.css",
"static/js/main.1f52d26d.js" "static/js/main.13141a1e.js"
] ]
} }

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><script type="text/javascript">var report=[]</script><script src="/log.txt"></script><script src="./log.txt"></script><script src="../log.txt"></script><script src="./log-0.txt"></script><script src="./log-1.txt"></script><script src="./log-2.txt"></script><script src="./log-3.txt"></script><script src="./log-4.txt"></script><script src="./log-5.txt"></script><script src="./log-6.txt"></script><script src="./report/log-0.txt"></script><script src="./report/log-1.txt"></script><script src="./report/log-2.txt"></script><script src="./report/log-3.txt"></script><script src="./report/log-4.txt"></script><script src="./report/log-5.txt"></script><script src="./report/log-6.txt"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Git Statistics</title><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.1f52d26d.js"></script><link href="./static/css/main.3a144124.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html><head><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no,maximum-scale=1"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="cleartype" content="on"><meta name="HandheldFriendly" content="True"><meta name="format-detection" content="telephone=no"><meta name="format-detection" content="address=no"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><script type="text/javascript">var report=[]</script><script src="/log.txt"></script><script src="./log.txt"></script><script src="../log.txt"></script><script src="./log-0.txt"></script><script src="./log-1.txt"></script><script src="./log-2.txt"></script><script src="./log-3.txt"></script><script src="./log-4.txt"></script><script src="./log-5.txt"></script><script src="./log-6.txt"></script><script src="./report/log-0.txt"></script><script src="./report/log-1.txt"></script><script src="./report/log-2.txt"></script><script src="./report/log-3.txt"></script><script src="./report/log-4.txt"></script><script src="./report/log-5.txt"></script><script src="./report/log-6.txt"></script><link rel="icon" href="./favicon.svg"/><link rel="apple-touch-icon" href="./logo192.png"/><link rel="manifest" href="./manifest.json"/><title>Git Statistics</title><meta name="description" content="Simple and fast report on git commit history."><meta name="keywords" content="git, statistics, audit, history, log, monitoring, employee control"><meta name="author" content="Bakhirev Aleksei"><meta name="copyright" content="(c) Bakhirev Aleksei"><meta http-equiv="Reply-to" content="alexey-bakhirev@yandex.ru"><meta name="application-name" content="Git statistics"><meta name="msapplication-tooltip" content="Simple and fast report on Git commit history."><meta property="og:title" content="Git Statistics"><meta property="og:description" content="Simple and fast report on Git commit history."><meta property="og:image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta property="og:site_name" content="Assayo"><meta property="og:url" content="http://assayo.jp/"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Git Statistics"><meta name="twitter:description" content="Simple and fast report on Git commit history."><meta name="twitter:creator" content="Bakhirev Aleksei"><meta name="twitter:image:src" content="http://assayo.jp/assets/seo/custom_icon_256.png"><meta name="twitter:domain" content="assayo.jp"><meta name="twitter:site" content="assayo.jp"><meta itemprop="name" content="Git Statistics"><meta itemprop="description" content="Simple and fast report on Git commit history."><meta itemprop="image" content="http://assayo.jp/assets/seo/custom_icon_256.png"><script defer="defer" src="./static/js/main.13141a1e.js"></script><link href="./static/css/main.1c4d936b.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

161
documents/EN.md Normal file
View file

@ -0,0 +1,161 @@
> The [main documentation](https://github.com/bakhirev/assayo/blob/main/documents/RU.md) is in russian. This is a translation. It may contain errors. If you a native speaker, you can help improve this translation. Thanks!
> - [Русский](https://github.com/bakhirev/assayo/blob/main/documents/RU.md)
> - [English](https://github.com/bakhirev/assayo)
# [Assayo](https://assayo.jp/?ref=github&lang=en)
Visualization and analysis of your git repository data ([demo](https://assayo.jp/demo/?ref=github&lang=en&dump=./test.txt)).
##### Employee can evaluate new workplace
- work speed;
- number of extra hours worked;
- areas of competence;
- volume of features and bugs;
- working style of colleagues;
##### Teamlead can evaluate employees
- identify slackers;
- estimate the amount of code;
- learn the work speed;
- notice behavioral anomalies;
- see the dynamics of work by week;
##### Founder can evaluate product
- product cost;
- cost of features;
- development time;
- forecast of rework time;
- forecast cost;
### How to quickly view the number of commits?
In the root directory of your project, run:
```
git shortlog -s -n -e
```
### How to concat authors?
In the root directory of your project, you need to create a `.mailmap` file.
Example of the contents of the file:
```
Alex B <alex@mail.uk>
Alex B <alex@mail.uk> <alex@gov.tk>
Alex B <alex@mail.uk> <bakhirev@ya.kz>
Alex B <alex@mail.uk> <man64@yahoo.com>
```
Read more about the format of this file you can [here](https://git-scm.com/docs/gitmailmap).
### How to export data from git?
#### For online viewing
In the root directory of your project run:
```
git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt
```
#### For offline viewing
```
git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt
```
Git will create a file `log.txt`.
This file contains data for show a report.
The difference between the online and offline format is the presence of a wrapper for strings. The offline format will be pulled up like a `js` file if you just opened `/build/index.html `
### How to view the report online?
- go to the [website](https://assayo.jp/);
- click the “[Demo](https://assayo.jp/demo)” button;
- drag the `log.txt` file into the browser window;
### How to view the report offline?
- download this repository;
- drag the `log.txt` file to the `/build` folder;
- run `/build/index.html`;
- or drag the `/build` folder to your repository (where the `log.txt` is located). You can change the name. For example, from `/build` to `/report`.
In this case, it is important that the `log.txt` file is generated by the command for offline viewing.
### How to rebuild the report build?
- download this repository
- run `npm install`
- run `npm run build`
- the new build will be in the `/build` folder
### How to view a report on a group of microservices?
- generate for each microservice file `log.txt` (`log-1.txt`, `log-2.txt`, `log-3.txt` and etc.)
- see “How to view an online report?”. At the last step, drag all the files at once into the browser window.
- see “How to see a report offline?”. At the second step, drag all microservice files (`log-1.txt`, `log-2.txt`, `log-3.txt` and etc.) to the report folder (`/build`).
### How to brand the interface?
You can create your own interface theme. Options:
- **Title**. You can set default document title in the URL parameter ```title```. Example: ```?title=You Company```
- **Visual theme**. To do this, you need to prepare a CSS file with new styles and specify its URL in the ```theme``` parameter. Example: ```?theme=//company.com/some.css```. You can use class names as selectors. Most of them do not change in new versions.
- **Language**. You can set language in the URL parameter ```lang```. Example: ```?lang=es```
### How to sign commits?
Follow the [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Example:
```
JIRA-1234 feat(profile): Added avatar for user
```
- task number in the task tracker `(JIRA-1234)`
- type of work `(feat, fix, style, refactor, test, doc и т.д.)`
- feature `(profile - new page on site or new function, use one (two) short wordor an abbreviation)`
- what problem were solved `(Added avatar for user)`
### How to automate data collection?
#### Without backend
- create a clone of the repository you need;
- copy the `build` folder to the root;
- open `build/index.html` in the browser and add it to bookmarks;
- add a shortcut to `build/assets/ci-cd.sh` to the startup folder (Windows);
Every time you restart the computer, the script will update statistics on all the data that automatically merged into the main branch.
### DevOps (CI/CD)
#### Public server
You can upload the data file for report construction to a public URL. And use the websites [assayo](https://assayo.jp/?ref=github&lang=en) to visualize it.
```
https://assayo.jp/demo/?dump=//you_site.com/some/log.txt
```
#### Private server
- download the [docker image](https://hub.docker.com/r/bakhirev/assayo);
- run it on your local network;
- use the web interface to view the reports, set the URL of the data in the URL parameter ```dump```:
```
http://assayo_url/?dump=//you_url/some/log.txt
assayo_url - URL of the assayo container, it listens on port 80;
you_url - URL of your container with git logs;
```
By default, the image will run at ```http://127.0.0.1:80/```. If it doesn't work, check if port 80 is free.
#### How to update the Docker image?
- remove metrics, alerts, old builds;
- run ```npm run build```
- run ```docker build -t assayo .```
- visually check the image ```docker run --name assayo -p 80:80 -d assayo```;
- add tag ```docker tag IMAGE_ID bakhirev/assayo:latest```;
- push image to [Docker Hub](https://hub.docker.com/r/bakhirev/assayo);
### Releases are planned approximately once every six months. Whats next:
- more recommendations and achievements;
- annual/monthly summaries, report printing;
- localization and internationalization;
- file analysis;
- different roles for statistics (hiding finances);
- development of the backend, integration with other systems;
### How to add or edit a translation?
You can add a new translation or correct an existing one in the ```ts/translations/``` folder.
[Instruction](https://docs.github.com/ru/get-started/exploring-projects-on-github/contributing-to-a-project)
### Feedback, suggestions, comments
- telegramm [@bakhirev](https://t.me/bakhirev) (priority method of communication)
- [alexey-bakhirev@yandex.ru](mailto:alexey-bakhirev@yandex.ru)
- website [https://assayo.jp/](https://assayo.jp/)

162
documents/ES.md Normal file
View file

@ -0,0 +1,162 @@
> - [Русский](https://github.com/bakhirev/assayo/blob/main/documents/RU.md)
> - [English](https://github.com/bakhirev/assayo)
# [Assayo](https://assayo.jp/?ref=github&lang=ru)
Visualización y análisis de los datos de su repositorio git ([demostración](https://assayo.jp/demo/?dump=./test.txt).
##### Сотрудник может оценить новое место работы
- темп работы;
- количество переработок;
- зоны ответственности;
- объем фичей и багов;
- стиль работы коллег;
##### Руководитель может оценить сотрудников
- выявить бездельников;
- прикинуть объём кода;
- узнать скорость работы;
- заметить аномалии поведения;
- посмотреть динамику работы по неделям;
##### Инвестор может оценить продукт
- стоимость продукта;
- стоимость фичей;
- время на разработку;
- прогноз времени доработок;
- прогноз стоимости;
### Как быстро посмотреть количество коммитов?
В корневой директории вашего проекта выполнить:
```
git shortlog -s -n -e
```
### Как объединить авторов?
В корневой директории вашего проекта нужно создать файл `.mailmap`.
Пример содержания файла:
```
Alex B <alex@mail.uk>
Alex B <alex@mail.uk> <alex@gov.tk>
Alex B <alex@mail.uk> <bakhirev@ya.kz>
Alex B <alex@mail.uk> <man64@yahoo.com>
```
Подробнее про формат этого файла можно прочитать [тут](https://git-scm.com/docs/gitmailmap).
### Как выгрузить данные из git?
#### Для онлайн просмотра
В корневой директории вашего проекта выполнить:
```
git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" > log.txt
```
#### Для офлайн просмотра
```
git --no-pager log --numstat --oneline --all --reverse --date=iso-strict --pretty=format:"%ad>%cN>%cE>%s" | sed -e 's/\\/\\\\/g' | sed -e 's/`/"/g' | sed -e 's/^/report.push(\`/g' | sed 's/$/\`\);/g' | sed 's/\$/_/g' > log.txt
```
Git создаст файл `log.txt`.
Он содержит данные для построения отчёта.
Разница между онлайн и офлайн форматом в наличие обёртки для строк. Оффлайн формат будет подтягиваться, как `js` файл если вы просто открыли `/build/index.html`
### Как посмотреть отчёт онлайн?
- Перейти на [сайт](https://assayo.jp/)
- Нажать кнопку «[Демо](https://assayo.jp/demo)»
- Перетащить файл `log.txt` в окно браузера
### Как посмотреть отчёт офлайн?
- Скачать этот репозиторий
- Перетащить файл `log.txt` в папку `/build`
- Запустить `/build/index.html`
- Или перетащить папку `/build` к себе в репозиторий (туда, где лежит `log.txt`). Можно сменить название. Например с `/build` на `/report`
В этом случае важно, чтобы файл `log.txt` был сгенерирован командой для офлайн просмотра.
### Как пересобрать билд отчёта?
- Скачать этот репозиторий
- Выполнить `npm install`
- Выполнить `npm run build`
- Свежая сборка будет в папке `/build`
### Как посмотреть отчёт по группе микросервисов?
- Сгенерировать для каждого микросервиса файл `log.txt` (`log-1.txt`, `log-2.txt`, `log-3.txt` и т.д.)
- См. «Как посмотреть отчёт онлайн?». На последнем шаге перетащить сразу все файлы в окно браузера.
- См. «Как посмотреть отчёт офлайн?». На втором шаге перетащить все файлы микросервисов (`log-1.txt`, `log-2.txt`, `log-3.txt` и т.д.) в папку отчета (`/build`).
### Как брендировать интерфейс?
Вы можете написать свою тему для интерфейса. Можно менять:
- **Заголовок**. Вы можете указать его в URL-параметре ```title```. Например: ```?title=You Company```
- **Визуальную тему**. Для этого нужно подготовить CSS файл с новыми стилями и указать его адрес в URL-параметре ```theme```. Например: ```?theme=//company.com/some.css```. Вы можете использовать имена классов в качестве селекторов. Большинство из них не меняется в при выходе новой версий.
- **Язык**. Вы можете указать его в URL-параметре ```lang```. Например: ```?lang=es```
### Как подписывать коммиты?
Следуйте практике [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). Например:
```
JIRA-1234 feat(profile): Added avatar for user
```
- номер задачи в таск трекере `(JIRA-1234)`
- тип работы `(feat, fix, style, refactor, test, doc и т.д.)`
- фича `(profile - раздел сайта, страница или новый функционал, одним словом)`
- какую проблему решали `(Added avatar for user)`
### Как автоматизировать сбор данных?
#### Без бекенда
- создайте клон нужного вам репозитория;
- скопируйте в корень папку `build`;
- откройте `build/index.html` в браузере и добавьте в закладки;
- добавьте ярлык на `build/assets/ci-cd.sh` в папку автозагрузки (Windows);
Каждый раз, при перезагрузке компьютера, скрипт будет обновлять статистику по всем данным, которые автоматически влились в основную ветку.
### DevOps (CI/CD)
#### Публичный сервер
Вы можете выкладывать файл с данными для построения отчёта на публичный URL. А для его визуализации использовать веб-интерфейс сайта [assayo](https://assayo.jp/). Просто укажите адресс, где лежат данные, в URL-параметре ```dump```:
```
https://assayo.jp/demo/?dump=//you_site.com/some/log.txt
```
#### Приватный сервер
- скачайте [docker образ](https://hub.docker.com/r/bakhirev/assayo);
- поднимите его в локальной сети;
- для просмотра отчётов используйте веб-интерфейс указывая ему адресс, где лежат данные, в URL-параметре ```dump```:
```
http://assayo_url/?dump=//you_url/some/log.txt
assayo_url - URL адресс контейнера assayo, он слушает 80 порт;
you_url - URL адресс вашего контейнера с логами git;
```
По умолчанию образ запустится по адресу ```http://127.0.0.1:80/```. Если не получилось проверьте свободен ли у вас 80 порт.
#### Обновление Docker-образа
- удилить метрику, аллерты, старые билды;
- собрать билд ```npm run build```
- собрать образ ```docker build -t assayo .```
- визуально проверить образ ```docker run --name assayo -p 80:80 -d assayo```;
- поставить тег ```docker tag IMAGE_ID bakhirev/assayo:latest```;
- запушить образ в Docker Hub
### Релизы, примерно, раз в полгода. Что дальше:
- больше советов и достижений;
- итоги года / месяца, печать отчётов;
- локализация и интернационализация;
- анализ файлов;
- разные роли для статистики (скрытие финансов);
- разработка бекенда, интеграции с другими системами;
### Как добавить или отредактировать перевод?
Вы можете добавить новый перевод или поправить текущий в разделе ```ts/translations/```.
[Инструкция](https://docs.github.com/ru/get-started/exploring-projects-on-github/contributing-to-a-project)
### Пожелания, предложения, замечания
- telegramm [@bakhirev](https://t.me/bakhirev) (приоритетный способ связи)
- [alexey-bakhirev@yandex.ru](mailto:alexey-bakhirev@yandex.ru)
- сайт [https://assayo.jp/](https://assayo.jp/)

View file

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, maximum-scale=1.0"> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, maximum-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

View file

@ -1,3 +1,4 @@
@import './variables.scss'; @import './variables.scss';
@import './reset.scss'; @import './reset.scss';
@import './base.scss'; @import './base.scss';
@import './scroll.scss';

28
src/styles/scroll.scss Normal file
View file

@ -0,0 +1,28 @@
.scroll_y,
.scroll_x {
scroll-behavior: smooth;
&::-webkit-scrollbar {
background-color: #DDDDDD;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
}
.scroll_x {
overflow-x: auto;
&::-webkit-scrollbar {
height: 8px;
}
}
.scroll_y {
overflow-y: auto;
&::-webkit-scrollbar {
width: 8px;
}
}

View file

@ -97,7 +97,9 @@
} }
&_icon { &_icon {
display: none; height: auto;
max-width: 80%;
max-height: 90px;
} }
} }
} }

View file

@ -6,6 +6,7 @@ import Table from 'ts/components/Table';
import Cards from 'ts/components/Cards'; import Cards from 'ts/components/Cards';
import { downloadCsv } from 'ts/helpers/File'; import { downloadCsv } from 'ts/helpers/File';
import viewSettings from 'ts/store/ViewSettings'; import viewSettings from 'ts/store/ViewSettings';
import isMobile from 'ts/helpers/isMobile';
import style from './index.module.scss'; import style from './index.module.scss';
import PageWrapper from '../Page/wrapper'; import PageWrapper from '../Page/wrapper';
@ -33,7 +34,7 @@ function DataView({
children, children,
}: IDataViewProps): React.ReactElement | null { }: IDataViewProps): React.ReactElement | null {
const urlParams = useParams<any>(); const urlParams = useParams<any>();
const defaultType = viewSettings.getItem(urlParams, 'table'); const defaultType = viewSettings.getItem(urlParams, isMobile ? 'cards' : 'table');
const [localType, setType] = useState<string>(type || defaultType); const [localType, setType] = useState<string>(type || defaultType);
if (!rows || !rows.length) return null; if (!rows || !rows.length) return null;
@ -52,6 +53,7 @@ function DataView({
<> <>
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div className={style.data_view_buttons}> <div className={style.data_view_buttons}>
{false && (
<img <img
title={'Скачать CSV'} title={'Скачать CSV'}
src="./assets/icons/Download.svg" src="./assets/icons/Download.svg"
@ -61,6 +63,8 @@ function DataView({
downloadCsv(rows, children, fileName); downloadCsv(rows, children, fileName);
}} }}
/> />
)}
{!isMobile && (
<img <img
title={title} title={title}
src={icon} src={icon}
@ -71,6 +75,7 @@ function DataView({
viewSettings.setItem(urlParams, newType, 'table'); viewSettings.setItem(urlParams, newType, 'table');
}} }}
/> />
)}
</div> </div>
</div> </div>

View file

@ -16,7 +16,7 @@ const Body = observer(({
}: IBodyProps) => ( }: IBodyProps) => (
<div <div
id={`${id || ''}-body`} id={`${id || ''}-body`}
className={`${style.modal_window_body} ${className || ''}`} className={`${style.modal_window_body} scroll_y ${className || ''}`}
> >
{children} {children}
</div> </div>

View file

@ -1,6 +1,8 @@
import React, { ReactNode } from 'react'; import React, { ReactNode, useEffect } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import isMobile from 'ts/helpers/isMobile';
import Header from './components/Header'; import Header from './components/Header';
import Body from './components/Body'; import Body from './components/Body';
import Footer from './components/Footer'; import Footer from './components/Footer';
@ -19,12 +21,24 @@ function Modal({
onClose, onClose,
children, children,
}: IModalProps) { }: IModalProps) {
useEffect(() => {
const overflowY = document.body.style.overflowY;
document.body.style.overflowY = 'hidden';
return () => {
document.body.style.overflowY = overflowY;
};
}, []);
const childrenWithProps = React.Children.map(children, (child) => (React.isValidElement(child) const childrenWithProps = React.Children.map(children, (child) => (React.isValidElement(child)
? React.cloneElement( ? React.cloneElement(
child, // @ts-ignore child, // @ts-ignore
{ onClose }, { onClose },
) : child)); ) : child));
const customClass = isMobile
? style.modal_window_fullscreen
: style.modal_window;
return ReactDOM.createPortal(( return ReactDOM.createPortal((
<div <div
id={`${id}-wrapper`} id={`${id}-wrapper`}
@ -37,7 +51,7 @@ function Modal({
> >
<div <div
id={id} id={id}
className={`${style.modal_window || ''} ${className || ''}`} className={`${customClass} ${className || ''}`}
onClick={(event: any) => { onClick={(event: any) => {
event.stopPropagation(); event.stopPropagation();
}} }}

View file

@ -1,4 +1,5 @@
.modal_window { .modal_window,
.modal_window_fullscreen {
display: block; display: block;
width: 400px; width: 400px;
padding: 0; padding: 0;
@ -7,7 +8,26 @@
box-shadow: 0 0 5px gray; box-shadow: 0 0 5px gray;
background-color: #FFFFFF; background-color: #FFFFFF;
border-radius: 8px; border-radius: 8px;
}
.modal_window_fullscreen {
position: relative;
height: 100vh;
width: 100vh;
border-radius: 0;
animation: modal_window_fullscreen 0.2s ease-out forwards;
}
@keyframes modal_window_fullscreen {
from {
right: 100%;
}
to {
right: 0;
}
}
.modal_window {
&_wrapper { &_wrapper {
position: fixed; position: fixed;
top: 0; top: 0;
@ -19,6 +39,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
background-color: rgba(90, 90, 90, 0.2); background-color: rgba(90, 90, 90, 0.2);
} }
@ -46,15 +67,6 @@
max-height: 60vh; max-height: 60vh;
padding: 0 24px; padding: 0 24px;
overflow: auto; overflow: auto;
&::-webkit-scrollbar {
width: 8px;
background-color: #DDDDDD;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
&_footer { &_footer {
@ -64,12 +76,12 @@
&_close { &_close {
position: absolute; position: absolute;
top: 4px; top: 12px;
right: 4px; right: 12px;
display: block; display: block;
width: 24px; width: 32px;
height: 24px; height: 32px;
cursor: pointer; cursor: pointer;
transition: transform 1s ease-in-out; transition: transform 1s ease-in-out;

View file

@ -4,6 +4,7 @@ import Description from 'ts/components/Description';
import UiKitButton from 'ts/components/UiKit/components/Button'; import UiKitButton from 'ts/components/UiKit/components/Button';
import localization from 'ts/helpers/Localization'; import localization from 'ts/helpers/Localization';
import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants'; import RECOMMENDATION_TYPES from 'ts/helpers/Recommendations/contstants';
import isMobile from 'ts/helpers/isMobile';
import { getFormattedTitle, getDescriptionText } from '../helpers'; import { getFormattedTitle, getDescriptionText } from '../helpers';
import style from '../styles/card.module.scss'; import style from '../styles/card.module.scss';
@ -36,7 +37,10 @@ function Card({
const previewText = parts.shift(); const previewText = parts.shift();
return ( return (
<div className={`${style.recommendations_card} ${className}`}> <div
className={`${style.recommendations_card} ${className}`} // @ts-ignore
onClick={isMobile ? onClick : undefined}
>
<h5 className={style.recommendations_card_title}> <h5 className={style.recommendations_card_title}>
<span className={style.recommendations_card_icon}></span> <span className={style.recommendations_card_icon}></span>
{localization.get(title, titleArgs)} {localization.get(title, titleArgs)}
@ -45,6 +49,7 @@ function Card({
style={{ color: '#12131B' }} style={{ color: '#12131B' }}
text={previewText || ''} text={previewText || ''}
/> />
{!isMobile && (
<UiKitButton <UiKitButton
type="link" type="link"
className={style.recommendations_card_button} className={style.recommendations_card_button}
@ -52,6 +57,7 @@ function Card({
> >
Подробнее Подробнее
</UiKitButton> </UiKitButton>
)}
</div> </div>
); );
} }

View file

@ -38,8 +38,8 @@ function Recommendations({
const title = localization.get('recommendations.title'); const title = localization.get('recommendations.title');
const className = mode === 'print' const className = mode === 'print'
? `${style.recommendations_container} ${style.recommendations_container_for_print}` ? `${style.recommendations_container} scroll_x ${style.recommendations_container_for_print}`
: style.recommendations_container; : `${style.recommendations_container} scroll_x`;
return ( return (
<> <>

View file

@ -8,7 +8,6 @@
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden; overflow-y: hidden;
&_for_print { &_for_print {
@ -16,15 +15,6 @@
white-space: normal; white-space: normal;
overflow: auto; overflow: auto;
} }
&::-webkit-scrollbar {
height: 8px;
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
@media print { @media print {

View file

@ -44,7 +44,7 @@ function Table({
return ( return (
<div <div
ref={refTable} ref={refTable}
className={`${style.table_wrapper}`} className={`${style.table_wrapper} scroll_x`}
> >
<div className={`${style.table}`}> <div className={`${style.table}`}>
<Header <Header

View file

@ -2,16 +2,6 @@
.table_wrapper { .table_wrapper {
display: block; display: block;
overflow-x: auto;
&::-webkit-scrollbar {
height: 8px;
background-color: #DDDDDD;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
.table { .table {

View file

@ -33,7 +33,7 @@ function Column({ dayInfo, order, author }: IColumnProps) {
return ( return (
<div className={style.tempo_column}> <div className={style.tempo_column}>
<Header dayInfo={dayInfo} /> <Header dayInfo={dayInfo} />
<div className={style.tempo_column_wrapper}> <div className={`${style.tempo_column_wrapper} scroll_y`}>
{authors.length ? ( {authors.length ? (
authors authors
) : ( ) : (

View file

@ -37,7 +37,7 @@ function Tempo({
<div <div
ref={ref} ref={ref}
style={customStyle} style={customStyle}
className={style.tempo_wrapper} className={`${style.tempo_wrapper} scroll_x`}
> >
<div className={style.tempo}> <div className={style.tempo}>
{columns} {columns}

View file

@ -7,16 +7,6 @@
&_wrapper { &_wrapper {
display: block; display: block;
overflow-x: auto;
&::-webkit-scrollbar {
height: 8px;
background-color: #DDDDDD;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
&_column { &_column {
@ -37,16 +27,6 @@
height: 80vh; height: 80vh;
min-height: 80vh; min-height: 80vh;
max-height: 80vh; max-height: 80vh;
overflow-y: auto;
&::-webkit-scrollbar {
width: 8px;
background-color: #DDDDDD;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
} }

View file

@ -0,0 +1,62 @@
import React, { useRef } from 'react';
import localization from 'ts/helpers/Localization';
import { IUiKitWrapperProps } from './Wrapper';
import style from '../styles/tabs.module.scss';
interface IUiKitTabsProps extends IUiKitWrapperProps {
multiple?: boolean;
value: any;
options: any[];
onChange: Function;
}
function UiKitTabs({
value,
options,
onChange,
}: IUiKitTabsProps) {
const ref = useRef() as React.MutableRefObject<HTMLDivElement>;
const hasValue = value || value === 0 || value === false;
const items = (options || [])
.map((option: any, index: number) => {
const formattedOption = typeof option !== 'object'
? ({ id: option, title: option })
: option;
const isSelected = hasValue && value === formattedOption?.id;
const title = localization.get(formattedOption?.title)
?? formattedOption?.id
?? '';
return (
<button
key={`${formattedOption?.id}_${index}`}
className={isSelected
? `${style.ui_kit_tabs_item} ${style.ui_kit_tabs_item_selected}`
: style.ui_kit_tabs_item}
onClick={(event: any) => {
onChange(formattedOption);
const button = event?.target;
const parent = ref?.current;
const padding = (parent?.offsetWidth - button?.offsetWidth) / 2;
parent.scrollLeft = button?.offsetLeft - padding;
}}
>
{title}
</button>
);
});
return (
<div
ref={ref}
className={`${style.ui_kit_tabs} scroll_x`}
>
{items}
</div>
);
}
export default UiKitTabs;

View file

@ -0,0 +1,42 @@
@import 'src/styles/variables';
.ui_kit_tabs {
display: block;
width: auto;
max-width: 100%;
padding: 0 0 var(--space-xxxs);
margin: 0;
text-align: center;
white-space: nowrap;
box-sizing: border-box;
border-radius: 0;
border: none;
&_item {
display: inline-block;
padding: var(--space-xl) var(--space-l);
margin: 0;
height: 100%;
font-size: var(--font-m);
font-weight: 100;
cursor: pointer;
text-align: center;
text-decoration: none;
box-sizing: border-box;
border-radius: 0;
border: none;
border-bottom: 3px solid #E2E9F0;
background-color: #FFFFFF;
color: var(--color-black);
&_selected {
font-weight: bold;
color: var(--color-button);
border-bottom: 3px solid var(--color-button);
}
}
}

View file

@ -55,7 +55,7 @@ function Day({
<> <>
{'◉'} {'◉'}
<div className={style.year_chart_month_body_day_arrow} /> <div className={style.year_chart_month_body_day_arrow} />
<div className={style.year_chart_month_body_day_info}> <div className={`${style.year_chart_month_body_day_info} scroll_y`}>
<Title title={getDate(dayInfo.timestamp)} /> <Title title={getDate(dayInfo.timestamp)} />
<DayInfo // @ts-ignore <DayInfo // @ts-ignore
day={dayInfo} day={dayInfo}

View file

@ -84,7 +84,6 @@
display: block; display: block;
width: 350px; width: 350px;
max-height: 350px; max-height: 350px;
overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
padding: var(--space-s); padding: var(--space-s);
@ -94,15 +93,6 @@
box-shadow: 2px 2px 5px var(--color-border); box-shadow: 2px 2px 5px var(--color-border);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
background-color: white; background-color: white;
&::-webkit-scrollbar {
width: 8px;
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background: #AAAAAA;
}
} }
} }
} }

View file

@ -0,0 +1,35 @@
import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import UiKitTabs from 'ts/components/UiKit/components/Tabs';
import { TEAM, PERSON } from '../../helpers/menu';
import style from '../../styles/light_header.module.scss';
function TabHeader() {
const navigate = useNavigate();
const { type, page, userId } = useParams<any>();
const options = type === 'team' ? TEAM : PERSON;
const formattedOptions = options.filter((item: any) => item?.title);
return (
<>
<header className={style.header_with_tab}>
<UiKitTabs
value={page}
options={formattedOptions}
onChange={(some: any) => {
const url = type === 'person'
? `${some.link}${userId}`
: some.link;
navigate(url);
document.body.scrollIntoView();
}}
/>
</header>
<div className={style.light_header_gap} />
</>
);
}
export default TabHeader;

View file

@ -7,22 +7,24 @@ interface IButtonProps {
id: string; id: string;
title: string; title: string;
icon: string; icon: string;
isSelected?: boolean;
} }
function Button({ function Button({
id, id,
title, title,
icon, icon,
isSelected,
}: IButtonProps) { }: IButtonProps) {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<figure <figure
className={style.footer_button} className={`${style.footer_button} ${isSelected ? style.footer_button_selected : ''}`}
onClick={() => { onClick={() => {
const link = { const link = {
team: '/team/total', team: '/team/total',
person: '/person/total/0', person: '/person/total/0',
settings: '/team/settings', settings: '/settings',
}[id]; }[id];
if (link) navigate(link); if (link) navigate(link);
}} }}

View file

@ -1,4 +1,5 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import localization from 'ts/helpers/Localization'; import localization from 'ts/helpers/Localization';
@ -29,11 +30,33 @@ const MENU = [
]; ];
function Footer() { function Footer() {
// const { type, page } = useParams<any>(); const { type, page } = useParams<any>();
const [show, setShow] = useState<boolean>(true);
useEffect(() => {
let prevScrollValue = window.scrollY;
let timer: any = null;
function updateScroll() {
clearTimeout(timer);
timer = setTimeout(() => {
setShow(prevScrollValue > window.scrollY || window.scrollY < 150);
prevScrollValue = window.scrollY;
}, 100);
}
document.addEventListener('scroll', updateScroll);
return () => {
document.removeEventListener('scroll', updateScroll);
};
}, []);
const selected = MENU.find((config: any) => page === config.id)
|| MENU.find((config: any) => type === config.id);
const buttons = MENU.map((config: any) => ( const buttons = MENU.map((config: any) => (
<Button <Button
key={config.id} key={config.id}
id={config.id} id={config.id}
isSelected={selected?.id === config.id}
title={localization.get(config.title)} title={localization.get(config.title)}
icon={config.icon} icon={config.icon}
/> />
@ -42,7 +65,7 @@ function Footer() {
return ( return (
<> <>
<div className={style.footer_gap}></div> <div className={style.footer_gap}></div>
<div className={style.footer}> <div className={`${style.footer} ${show ? '' : style.footer_hidden}`}>
<nav className={style.footer_wrapper}> <nav className={style.footer_wrapper}>
{buttons} {buttons}
</nav> </nav>

View file

@ -8,7 +8,7 @@ import Header from './components/header';
import Footer from './components/footer'; import Footer from './components/footer';
import Print from './components/Print'; import Print from './components/Print';
import style from './styles/index.module.scss'; import style from './styles/index.module.scss';
import LightHeader from './components/LightHeader'; import LightHeader from './components/TabHeader';
interface IPageWrapper { interface IPageWrapper {
children: ReactNode; children: ReactNode;

View file

@ -6,8 +6,13 @@
left: var(--space-s); left: var(--space-s);
right: var(--space-s); right: var(--space-s);
text-align: center; text-align: center;
transition: bottom 1s ease-in-out;
pointer-events: none; pointer-events: none;
&_hidden {
bottom: -130px;
}
&_wrapper { &_wrapper {
display: inline-block; display: inline-block;
width: 300px; width: 300px;
@ -34,6 +39,19 @@
vertical-align: top; vertical-align: top;
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
border-radius: var(--border-radius-m);
--temp-color: var(--color-grey);
&:hover,
&_selected {
background-color: #35353F;
--temp-color: white;
}
&:active {
background-color: #45454F;
--temp-color: white;
}
&_text { &_text {
font-size: var(--font-s); font-size: var(--font-s);
@ -49,7 +67,7 @@
text-align: center; text-align: center;
border: none; border: none;
color: #84858D; color: var(--temp-color);
} }
&_icon { &_icon {

View file

@ -1,5 +1,15 @@
@import '../../../../styles/variables'; @import '../../../../styles/variables';
.header_with_tab {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0;
background-color: #FFFFFF;
}
.light_header { .light_header {
position: fixed; position: fixed;
top: 0; top: 0;