# Custis grid

Грид для отображения данных. Используется в основных модулях. Поддерживает много функций (фильтрация, сортировка, пагинация, изменение отображения столбцов, выделение строк, и т.п.). Построен на основе библиотеки react-table.

[![Latest version](https://img.shields.io/npm/v/@custis/grid)](https://www.npmjs.com/package/@custis/grid)

Основные компоненты библиотеки:

- компонент Grid (Grid)
- стор с данными для этого компонента GridStore
- фабрика для столбцов ColumnFactory
- модели данных для запроса и результата

Чтобы встроить грид в проект, нужно отнаследоваться от класса GridStore и описать способы получения данных для грида. Затем, добавить на нужную страницу компонент Grid, в пропсы передать полученный стор. Также, необходимо сконфигурировать и передать в пропсы массив столбцов - создаём их через фабрику сolumnFactory.create(...).

## Быстрый старт

1. Возьмите токен (см. ниже) и добавьте его в переменную окружения NPM_TOKEN
2. Установите зависимости `npm install`
3. Соберите проект `npm run build`

Чтобы добавить изменения:

1. Сделайте изменения, напишите тесты
2. Проверьте сборку `npm run build`
3. Запушьте в отдельную ветку
4. После CR вмёржите ветку в мастер
5. Поднимите версию приложения `npm version x.y.z`. Нумерация в соответствии с [semver](https://semver.org/lang/ru/)
6. Запушьте с тегами

## Общее

### Используемые технологии, библиотеки и кодстайл

#### [React](https://ru.reactjs.org/)

Основная библиотека для UI. Стараемся обновлять до актуальной версии.

Используем функциональные компоненты. При написании кода выносим всю ui-логику в хуки (даже если логика не
переиспользуется). Компоненты делаем максимально простыми. Бизнес логику выносим в mobx-store.

В библиотеках тип пропсов экспортируем, в приложениях можно не экспортировать.

```tsx
interface MyComponentProps {
  foo: string;
}

export const MyComponent: FC<MyComponentProps> = (props) => {
  const { foo } = props;
  const { someHandler, someField, store } = useMyComponent(foo);
  return (
    <div>
      <button onClick={someHandler}>Click</button>
      <p>{someField}</p>
      <OtherComponent store={store} />
    </div>
  );
};
```

#### [Tsyringe](https://github.com/microsoft/tsyringe)

Библиотека для DI. На её основе выстраиваем DI-систему в приложениях и библиотеках. Классы с логикой помечаем
декоратором `injectable`. Стараемся использовать в качестве токенов типы, а не строки. К сожалению, в тайпскрипте нет
возможности использовать интерфейсы как токен, так что если есть острая необходимость использовать интерфейсы, то
используем строку как токен и внедряем такую зависимость через аннотацию `inject` в конструктор. Внедрять классы в
классы просто - достаточно указать зависимость в конструкторе. Чтобы внедрить сервис используйте хук `useInjection` из
библиотеки `@custis/common`:

```tsx
@injectable
export class BackendService {
  async getItems(): Promise<Item[]> {
    // ...
  }
}

@injectable
export class SomeStore {
  // Внедрение в класс
  constructor(private backendService: BackendService) {}
}

export const MyComp: FC = () => {
  // Внедрение в компонент
  const store = useInjection(SomeStore);
  return <></>;
};
```

#### [MobX](https://mobx.js.org/README.html)

Библиотека для хранения состояния. Бизнес логику, связанную с состоянием реализуем в классах-сторах. В этих классах
отслеживаемые поля помечаем как `observable`, вычисляемые поля (геттеры) помечаем как `computed`, методы, изменяющие
состояние помечаем как `action`. Сами сторы делаем `injectable`, чтобы они были доступны для DI.

```typescript
@injectable
export class SomeStore {
  @observable items: Item[] = [];
  @observable loading: boolean = false;

  constructor(private backendService: BackendService) {}

  @action
  async loadItems(): Promise<void> {
    this.loading = true;

    const result = await this.backendService.getItems();

    this.items = result.items;
    this.loading = false;
  }
}
```

### Линтеры и форматтеры

В проектах за качеством кода следят линтеры. Перед началом работы убедитесь, что ваш редактор или IDE настроены на
работу с ними.

#### Линтер кода - Eslint

В качестве линтера используется [eslint](https://eslint.org/). Он настроен на проверку правил typescript (проверяет
правила типизации), и реакт (следит за именованием компонентов и хуками). Выставлены достаточно строгие правила проверки
кода.

Проверьте, что ваш редактор использует eslint. Для этого, например, в любом ts-файле можете добавить такой код. В
комментариях показаны места, где линтер должен оповестить об ошибках.

```typescript
function a(arg1: any) {
  //      ^-- @typescript-eslint/no-explicit-any
  let var1 = 1;
  //  ^-- prefer-const
  console.log(var1);
  return arg;
  //  ^-- @typescript-eslint/no-unsafe-return
}
```

Если в редакторе появятся ошибки, то линтер настроен.

Для запуска линтера по всему проекту есть npm-скрипты:

- `npm run check:linting` для отлова ошибок. Если в проекте есть хоть одна ошибка, запуск команды завершится с ненулевым
  кодом.
- `npm run check-extra:linting-with-warn` для отлова ошибок и предупреждений. Если в проекте больше чем 10
  предупреждений, запуск команды завершится ошибкой.

Для исправлений замечаний eslint запустите команду `npm run fix:lint`

#### Форматтер кода - Prettier

В проекте для контроля форматирования кода используется prettier. В принципе, eslint может следить и исправлять
отклонения от стиля кода, но prettier делает это лучше. В редакторе желательно настроить форматирование кода через
prettier при сохранении. Для проверки всего проекта на соответствие формату кода используйте
команду `npm run check:format`. Если какие-то файлы не соответствуют формату, то команда завершится с ненулевым кодом.
Для исправления форматирования по всему проекту есть команда `npm run fix:format`

#### Хуки гита

При запуске команды `npm install` запускается настройка [husky](https://github.com/typicode/husky) - средства для
управления хуками в гит. При коммите запускаются 2 npm-скрипта

- `npm run fix` - исправление кода, сам скрипт запускает следующие скрипты
  - `npm run fix:lint` - исправления линтера
  - `npm run fix:format` - исправления форматтера
- `npm run check` - проверка кода, сам скрипт запускает все скрипты, начинающиеся на `check:`:
  - `npm run check:types` - проверка корректности типов, позволяет без сборки проверить код на корректность
    используемых типов
  - `npm run check:format` - форматирование
  - `npm run check:lint` - кодстайл

Если секция `npm run check` завершится с ненулевым кодом, то коммит не произойдёт. Если всё-же нужно закоммитить код,
который не проходить проверки, то нужно при коммите добавить опцию `--no-verify`.

### Сборка проекта

Сборка любых проектов выполняется командой `npm run build`. При этом, до запуска самой сборки запускается секция
проверки кода и тесты (пока только в библиотеках).

#### Сборка приложений

Для сборки приложений используется webpack. За основу взят конфиг из `create-react-app`, у которого выполнен eject. Из
изменений:

- сборщик для typescript заменён на `awesome-typescript-loader`
- добавлена возможность использовать [css-модули](https://github.com/css-modules/css-modules) для less-файлов

#### Сборка библиотек

Для сборки библиотек используется rollup с плагинами. Есть особенность сборки библиотек и указания зависимостей. Если вы
указываете какую-то библиотеку в зависимостях, то иногда плагин commonjs не может разрешить некоторые зависимости и
сборка падает с не очень внятной ошибкой. Есть 2 решения это проблемы:

1. Явно указать экспортируемые символы в `rollup.config.js`, например:
   ```typescript
   commonjs({
     namedExports: {
       'node_modules/react/react.js': ['Children', 'Component', 'PropTypes', 'createElement'],
     },
   });
   ```
2. Убрать библиотеку из зависимостей (dependencies в package.json), но добавить её в devDependencies и в
   peerDependencies. Добавление в devDependencies даст возможность сборки на стенде, указание в peerDependencies
   проинформирует о необходимости установки этих зависимостей в приложение, использующее библиотеку

#### Линковка библиотек в проект

Чтобы протестировать работу библиотеки в проекте локально, можно прилинковать её в `node_modules` проекта. В обычном
варианте это делается так:

1. Заходим в директорию библиотеки
2. Собираем её
3. Запускаем команду `npm link`
4. Заходим в директорию проекта
5. Запускаем команду `npm link @custis/library_name`
6. Запускаем проект

Однако, из-за особенностей работы библиотек для DI, tsyringe в нашем случае (грубо говоря, такие библиотеки не
поддерживают символические ссылки), этот способ не подходит. Можно воспользоваться скриптом в приложении, который сам
соберёт нужные библиотеки и скопирует их внутрь папки node_modules. Чтобы он заработал, достаточно скопировать
файл `libs.config.sample.js` в новый файл с именем `libs.config.js`. В нём нужно указать для выбранной библиотеки путь
до неё относительно приложения, в котором мы хотим протестировать эту библиотеку. После этого нужно

1. Собрать библиотеку
2. Запустить в приложении команду `npm run link`
3. Запустить проект

Чтобы удалить локальную копию библиотеки из node_modules, можно выполнить команду `npm run unlink` или просто запустить
заново `npm install`

### Настройка стендов

Для связи с бэкендом используется прокси. В файле `src/dev.proxy.config.js` описаны пути до стендов. По-умолчанию, для
запуска ничего дополнительно настриаивать не нужно. Однако, вы можете переопределить настройки прокси локально, для
этого достаточно создать файл `src/dev.proxy.conf.local.js` в проекте с содержимым,
аналогичным `src/dev.proxy.config.js`.

### Настройка .npmrc

- Для библиотек с зависимостями от других библиотек custis необходим токен аутентификации. Лучше всего не хранить его в
  репозитории, а добавить в переменные окружения. Сам токен берём из настроек проекта в
  гитлабе (`Settings -> CI/CD -> Variables`). Если такой вкладки нет - попросите дать доступ к этой части у админов.
  Конечный файл `.npmrc` должен вглядеть так:
- Для приложений достаточно указать отпечаток
- Для всех проектов нужно указать адрес репозитория. Для префикса @custis всегда указываем nexus, для остальных пакетов
  можно указать как нексус, так и оригинальный npmjs

Пример файла `.npmrc` для библиотеки

```
@custis:registry=https://nexus.custis.ru/repository/npm-hosted/
registry=https://registry.npmjs.org/
always-auth=true
//nexus.custis.ru/repository/npm-hosted/:_authToken=${NPM_TOKEN}
```

Пример для приложения

```
@custis:registry=https://nexus.custis.ru/repository/npm-hosted/
registry=https://nexus.custis.ru/repository/npm-proxy/
_auth=dG4tcmVhZG9ubHk6M2tMQE9HMmw=

always-auth=true
```
