@sberbusiness/triplex-next-mcp-server 1.42.0-beta → 1.44.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/components/AlertProcess.md +3 -2
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/Button.md +1 -0
- package/bundle/components/Checkbox.md +7 -1
- package/bundle/components/CheckboxTreeExtended.md +149 -248
- package/bundle/components/CheckboxXGroup.md +2 -0
- package/bundle/components/CheckboxYGroup.md +7 -2
- package/bundle/components/ChipGroup.md +195 -126
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/Island.md +36 -1
- package/bundle/components/LightBox.md +14 -3
- package/bundle/components/MediaWidth.md +7 -1
- package/bundle/components/Notification.md +21 -3
- package/bundle/components/Overlay.md +24 -1
- package/bundle/components/Pagination.md +12 -13
- package/bundle/components/SliderExtended.md +358 -222
- package/bundle/components/TableBasic.md +2294 -0
- package/bundle/components/TabsExtended.md +380 -194
- package/bundle/components/Tag.md +259 -78
- package/bundle/components/TopOverlay.md +366 -90
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/components/UnorderedList.md +254 -0
- package/bundle/components/UnorderedListExtended.md +255 -0
- package/bundle/components/WindowResizeListener.md +249 -0
- package/bundle/guides/commits.md +121 -15
- package/bundle/guides/stories.md +1 -1
- package/bundle/manifest.json +46 -20
- package/bundle/release-notes/1.43.0.mdx +87 -0
- package/bundle/release-notes/1.44.0.mdx +120 -0
- package/bundle/release-notes/1.45.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2294 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: TableBasic
|
|
3
|
+
category: Table
|
|
4
|
+
related: [MasterTable, ListTableItem, LoaderScreen, LoaderMiddle, Text]
|
|
5
|
+
tokens:
|
|
6
|
+
- --triplex-next-TableBasic-Header_Background
|
|
7
|
+
- --triplex-next-TableBasic-Color
|
|
8
|
+
- --triplex-next-TableBasic-Background_Hover
|
|
9
|
+
- --triplex-next-TableBasic-Background_Selected
|
|
10
|
+
stories: stories/TableBasic/TableBasic.stories.tsx
|
|
11
|
+
version: "1.0"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# TableBasic
|
|
15
|
+
|
|
16
|
+
## Назначение
|
|
17
|
+
|
|
18
|
+
Табличное представление данных: `<table>` с шапкой из `columns`, строками из
|
|
19
|
+
`data` и подвалом, в котором показывается заглушка пустого состояния или лоадер.
|
|
20
|
+
Обвязку вокруг таблицы — панель чипов, фильтры, настройки колонок, футер
|
|
21
|
+
выделения и пагинацию — добавляет родительский `MasterTable`; из его контекста
|
|
22
|
+
`TableBasic` берёт состояние загрузки и публикует в него свой набор колонок.
|
|
23
|
+
|
|
24
|
+
Используй когда: нужен структурированный набор данных в строках и столбцах —
|
|
25
|
+
реестр документов, выписка, список операций.
|
|
26
|
+
|
|
27
|
+
Не используй когда:
|
|
28
|
+
- нужен список карточек с собственной вёрсткой строки, свайпом или
|
|
29
|
+
drag-and-drop — бери `List` / `ListTableItem`;
|
|
30
|
+
- таблица должна работать без `MasterTable`. Формально это возможно (контекст
|
|
31
|
+
имеет значения по умолчанию), но тогда не работают `loading` и настройки
|
|
32
|
+
колонок — это не поддерживаемый сценарий.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Варианты и props
|
|
37
|
+
|
|
38
|
+
`TableBasic` доступен и напрямую, и как `MasterTable.TableBasic` — это один и
|
|
39
|
+
тот же компонент.
|
|
40
|
+
|
|
41
|
+
### Обязательные props
|
|
42
|
+
|
|
43
|
+
| Prop | Тип | Описание |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `columns` | `ITableBasicColumn[]` | Описание столбцов: ключ поля, заголовок, выравнивание, ширина, сортировка |
|
|
46
|
+
| `data` | `ITableBasicRow[]` | Строки таблицы. Пустой массив — рендерится `renderNoData` |
|
|
47
|
+
| `renderNoData` | `() => React.JSX.Element` | Заглушка для пустой таблицы. Обязателен даже там, где данные всегда есть |
|
|
48
|
+
|
|
49
|
+
### Опциональные props
|
|
50
|
+
|
|
51
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `renderNoColumns` | `() => React.ReactNode` | — | Заглушка вместо таблицы, когда все `columns` скрыты (`hidden`) |
|
|
54
|
+
| `highlightRowOnHover` | `boolean` | `false` | Подсветка строки под курсором |
|
|
55
|
+
| `onOrderBy` | `(order: ISortOrder) => void` | — | Обработчик сортировки. Включает кнопку сортировки у колонок с `orderDirection` |
|
|
56
|
+
| `onClickRow` | `(rowKey: string) => void` | — | Клик по строке. Включает `cursor: pointer` и подсветку при наведении |
|
|
57
|
+
| `headless` | `boolean` | `false` | Скрыть шапку таблицы |
|
|
58
|
+
| `...HTMLTableElementAttributes` | — | — | Стандартные атрибуты `<table>`, включая `className`; `ref` — на `<table>` (в ветке `renderNoColumns` таблицы нет, ref пустой) |
|
|
59
|
+
|
|
60
|
+
### `ITableBasicColumn`
|
|
61
|
+
|
|
62
|
+
| Поле | Тип | По умолчанию | Описание |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `fieldKey` | `string` | — | Ключ в `rowData`, значение которого выводится в ячейке |
|
|
65
|
+
| `label` | `string \| React.JSX.Element` | — | Содержимое заголовка |
|
|
66
|
+
| `title` | `string` | — | Нативный `title` у `<th>` |
|
|
67
|
+
| `orderDirection` | `EOrderDirection` | — | Текущее направление. Наличие значения + `onOrderBy` включают сортировку |
|
|
68
|
+
| `horizontalAlign` | `EHorizontalAlign` | `LEFT` | Горизонтальное выравнивание |
|
|
69
|
+
| `verticalAlign` | `EVerticalAlign` | `BASELINE` для `TEXT`, `TOP` для остальных | Вертикальное выравнивание |
|
|
70
|
+
| `cellType` | `ECellType` | `TEXT` | Отступы ячейки и способ вывода контента |
|
|
71
|
+
| `width` | `string \| number` | — | У `<th>` задаёт `min/max/width`, у `<td>` — только `width` |
|
|
72
|
+
| `hidden` | `boolean` | `false` | Столбец не рендерится ни в шапке, ни в строках |
|
|
73
|
+
| `renderCell` | `(param: any) => React.ReactNode` | — | Кастомный рендер значения ячейки |
|
|
74
|
+
| `dataAttributes` / `ariaAttributes` | `TDataHTMLAttributes` / `TAriaHTMLAttributes` | — | Ключи без префикса: `{label: "..."}` → `aria-label="..."` |
|
|
75
|
+
|
|
76
|
+
### `ITableBasicRow`
|
|
77
|
+
|
|
78
|
+
| Поле | Тип | По умолчанию | Описание |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `rowKey` | `string` | — | React-key строки, он же аргумент `onClickRow` |
|
|
81
|
+
| `rowData` | `any` | — | Объект «ключ колонки → значение ячейки» |
|
|
82
|
+
| `rowLayout` | `Record<string, ITableRowCellSpanProps>` | — | `rowSpan` / `colSpan` по ключу колонки |
|
|
83
|
+
| `selected` | `boolean` | `false` | Подсветка выбранной строки. Компонент сам флаг не проставляет |
|
|
84
|
+
| `dataAttributes` / `ariaAttributes` | `TDataHTMLAttributes` / `TAriaHTMLAttributes` | — | Атрибуты `<tr>`; `test-id` дополнительно строит `data-test-id` ячеек |
|
|
85
|
+
|
|
86
|
+
### Ограничения по типам ячеек
|
|
87
|
+
|
|
88
|
+
- `ECellType.TEXT` (по умолчанию) — значение оборачивается в `Text` размера `B3`.
|
|
89
|
+
- `ECellType.COMPONENTS` и `ECellType.CHECKBOX` — разметка выводится как есть,
|
|
90
|
+
меняются только внутренние отступы ячейки.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Дизайн-токены
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
--triplex-next-TableBasic-Header_Background
|
|
98
|
+
--triplex-next-TableBasic-Color
|
|
99
|
+
--triplex-next-TableBasic-Background_Hover
|
|
100
|
+
--triplex-next-TableBasic-Background_Selected
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Остальные токены группы `TableBasic-*` (`TableFooter_*`, `TableBasicSettings_*`)
|
|
104
|
+
принадлежат соседним компонентам семейства, а не самому `TableBasic`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Инварианты
|
|
109
|
+
|
|
110
|
+
- **Корневой DOM — `<div>` со стилями таблицы, а `ref` и остальные HTML-атрибуты
|
|
111
|
+
идут на вложенный `<table>`.** Так исторически ведёт себя `className`, и
|
|
112
|
+
`forwardRef` добавлен туда же. Переносить их на корневой `<div>` — breaking
|
|
113
|
+
change. Проброс не безусловный: когда все колонки скрыты и задан
|
|
114
|
+
`renderNoColumns`, вместо таблицы рендерится заглушка и `ref.current` остаётся
|
|
115
|
+
`null` — измерять таблицу по ref в этом состоянии нельзя.
|
|
116
|
+
- **`renderNoData` обязателен.** Сделать его опциональным можно только вместе с
|
|
117
|
+
решением, что рендерится при пустых данных без него.
|
|
118
|
+
- **Ячейка рендерится, только если ключ колонки есть в `rowData`.** Колонка без
|
|
119
|
+
соответствующего ключа не даёт `<td>` вовсе — строка окажется короче шапки.
|
|
120
|
+
Для пустой ячейки передавай `null`, а не отсутствие ключа.
|
|
121
|
+
- **Пустое значение ячейки заменяется плейсхолдером `---`.** Проверка falsy,
|
|
122
|
+
поэтому `0`, `""` и `false` тоже дают `---`. Менять это правило —
|
|
123
|
+
наблюдаемое изменение поведения.
|
|
124
|
+
- **Компонент не сортирует данные.** `onOrderBy` вызывается со **следующим**
|
|
125
|
+
направлением по циклу `none → asc → desc → none`; сортировку выполняет
|
|
126
|
+
потребитель и возвращает новый `orderDirection` в колонке.
|
|
127
|
+
- **Колонки публикуются в `MasterTableContext`** через `useEffect` с
|
|
128
|
+
`isEqual`-сравнением — на нём держатся настройки колонок
|
|
129
|
+
(`TableBasicSettings` / `ColumnSettings`). Собирай `columns` стабильно
|
|
130
|
+
(по возможности вне рендера), иначе таблица будет лишний раз обновлять
|
|
131
|
+
контекст.
|
|
132
|
+
- **`loading` приходит только из контекста `MasterTable`**, собственного prop'а
|
|
133
|
+
у таблицы нет.
|
|
134
|
+
- **Класс `selected` на `<tr>` выставляется дважды** — хешированный из
|
|
135
|
+
CSS-модуля и глобальный `"selected"`. Глобальный оставлен для внешних стилей
|
|
136
|
+
и тестов, удалять нельзя.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Accessibility
|
|
141
|
+
|
|
142
|
+
- Разметка нативная: `<table>` / `<thead>` / `<tbody>` / `<th>` / `<td>`,
|
|
143
|
+
скринридер читает её как таблицу без дополнительных ролей.
|
|
144
|
+
- Собственных `aria-*` компонент не хардкодит — библиотека мультиязычная.
|
|
145
|
+
`aria-label` таблицы передаётся через props (уходит на `<table>`), атрибуты
|
|
146
|
+
столбца и строки — через `ariaAttributes` (ключи без префикса `aria-`).
|
|
147
|
+
- **Сортировка не доступна с клавиатуры.** Заголовок с сортировкой — это
|
|
148
|
+
`<span>` с `onClick`, без `role="button"`, `tabIndex` и обработки
|
|
149
|
+
`Enter` / `Space`; `aria-sort` не выставляется. То же с `onClickRow`: клик по
|
|
150
|
+
`<tr>` не имеет клавиатурного эквивалента. Если нужен доступный вариант —
|
|
151
|
+
клади в ячейку интерактивный элемент через `renderCell`.
|
|
152
|
+
- Иконка направления сортировки в состоянии `NONE` видна только при наведении на
|
|
153
|
+
заголовок — состояние сортировки передаётся только цветом и формой иконки.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Связанные компоненты
|
|
158
|
+
|
|
159
|
+
- `MasterTable` — родитель семейства: провайдер контекста (`loading`, `columns`)
|
|
160
|
+
и контейнер для панелей. Предоставляет `MasterTable.TableBasic`,
|
|
161
|
+
`MasterTable.FilterPanel`, `MasterTable.ChipPanel`,
|
|
162
|
+
`MasterTable.TableBasicSettings`, `MasterTable.TableFooter`,
|
|
163
|
+
`MasterTable.PaginationPanel`, `MasterTable.NoColumns`.
|
|
164
|
+
- `ListTableItem` — альтернатива для табличного списка, когда строка это
|
|
165
|
+
карточка со своей вёрсткой, свайпом и контролами.
|
|
166
|
+
- `LoaderScreen` — рендерится поверх таблицы, когда `loading` пришёл из
|
|
167
|
+
контекста, а данные уже есть.
|
|
168
|
+
- `LoaderMiddle` — рендерится в подвале, когда идёт загрузка и данных ещё нет.
|
|
169
|
+
- `Text` — в него оборачивается содержимое заголовков и ячеек типа
|
|
170
|
+
`ECellType.TEXT`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Stories
|
|
175
|
+
|
|
176
|
+
Основные истории: `stories/TableBasic/TableBasic.stories.tsx`
|
|
177
|
+
Файлы примеров: `stories/TableBasic/examples/`
|
|
178
|
+
|
|
179
|
+
| Story | Example file | Что демонстрирует |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль загрузки, шапки, подсветки, данных, скролла и кнопок |
|
|
182
|
+
| `Default` | `Default.tsx` | Минимальная таблица: `columns`, `data`, `renderNoData` |
|
|
183
|
+
| `Alignments` | `Alignments.tsx` | `horizontalAlign` и `verticalAlign` и их значения по умолчанию |
|
|
184
|
+
| `CellTypes` | `CellTypes.tsx` | `ECellType.TEXT` / `COMPONENTS` / `CHECKBOX` |
|
|
185
|
+
| `Sorting` | `Sorting.tsx` | Цикл `none → asc → desc → none`, сортировка на стороне потребителя |
|
|
186
|
+
| `Headless` | `Headless.tsx` | Таблица без шапки |
|
|
187
|
+
| `HighlightRowOnHover` | `HighlightRowOnHover.tsx` | Подсветка строки под курсором |
|
|
188
|
+
| `ClickableRows` | `ClickableRows.tsx` | `onClickRow` и флаг `selected` в данных строки |
|
|
189
|
+
| `NoData` | `NoData.tsx` | Заглушка пустой таблицы |
|
|
190
|
+
| `NoColumns` | `NoColumns.tsx` | Заглушка, когда все колонки скрыты |
|
|
191
|
+
| `Loading` | `Loading.tsx` | Загрузка поверх данных и загрузка при пустой таблице |
|
|
192
|
+
| `TableSpan` | `TableSpan.tsx` | Объединение ячеек через `rowLayout` |
|
|
193
|
+
| `TableSettingsColumn` | `TableSettingsColumn.tsx` | Настройка видимости и порядка колонок |
|
|
194
|
+
| `TableSettingsColumnExtended` | `TableSettingsColumnExtended.tsx` | Настройка не только колонок, но и блоков внутри колонки |
|
|
195
|
+
| `TableWithPagination` | `TableWithPagination.tsx` | Таблица с `Pagination` при известном количестве данных |
|
|
196
|
+
| `TableWithPaginationLoading` | `TableWithPaginationLoading.tsx` | Фильтры, таблица и пагинация в состоянии загрузки |
|
|
197
|
+
| `TableWithPaginationExtended` | `TableWithPaginationExtended.tsx` | Таблица с `PaginationExtended` при неизвестном количестве данных |
|
|
198
|
+
| `Example: production` | `ExampleProduction.tsx` | Композиция, приближённая к production: чипы-сегменты, фильтры, теги |
|
|
199
|
+
| `VisualTests` | `VisualTests.tsx` | Статичные визуальные состояния в одном кадре |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## История изменений
|
|
204
|
+
|
|
205
|
+
| Дата | Изменение |
|
|
206
|
+
|---|---|
|
|
207
|
+
| 2026-08-27 | Создан документ. AI-рефакторинг: `TableBasic` получил `forwardRef` на `<table>`, JSDoc на всех публичных полях, разбор подвала по состояниям, удалён неиспользуемый `TableBasicAdaptive.module.less`. Stories переведены на modern pattern, добавлены unit-тесты (46) |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Примеры использования
|
|
212
|
+
|
|
213
|
+
### Default
|
|
214
|
+
|
|
215
|
+
`stories/TableBasic/examples/Default.tsx`
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import React from "react";
|
|
219
|
+
import { ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
220
|
+
|
|
221
|
+
const columns: ITableBasicColumn[] = [
|
|
222
|
+
{ fieldKey: "number", label: "Номер", title: "Номер", width: 100 },
|
|
223
|
+
{ fieldKey: "recipient", label: "Получатель", title: "Получатель" },
|
|
224
|
+
{ fieldKey: "sum", label: "Сумма", title: "Сумма", width: 140 },
|
|
225
|
+
{ fieldKey: "status", label: "Статус", title: "Статус", width: 140 },
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const data: ITableBasicRow[] = [
|
|
229
|
+
{ rowKey: "1350", rowData: { number: "1350", recipient: "ООО Ромашка", sum: "9 450,00", status: "Создан" } },
|
|
230
|
+
{ rowKey: "1351", rowData: { number: "1351", recipient: "ИП Иванов И. И.", sum: "18 914,00", status: "Подписан" } },
|
|
231
|
+
{ rowKey: "1352", rowData: { number: "1352", recipient: "ООО Росинка", sum: "28 392,00", status: "Оплачен" } },
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
export const Default = () => (
|
|
235
|
+
<MasterTable>
|
|
236
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
237
|
+
</MasterTable>
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Alignments
|
|
242
|
+
|
|
243
|
+
`stories/TableBasic/examples/Alignments.tsx`
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import React from "react";
|
|
247
|
+
import {
|
|
248
|
+
EHorizontalAlign,
|
|
249
|
+
EVerticalAlign,
|
|
250
|
+
ITableBasicColumn,
|
|
251
|
+
ITableBasicRow,
|
|
252
|
+
MasterTable,
|
|
253
|
+
} from "@sberbusiness/triplex-next";
|
|
254
|
+
|
|
255
|
+
const columns: ITableBasicColumn[] = [
|
|
256
|
+
{
|
|
257
|
+
fieldKey: "left",
|
|
258
|
+
label: "Слева (по умолчанию)",
|
|
259
|
+
horizontalAlign: EHorizontalAlign.LEFT,
|
|
260
|
+
verticalAlign: EVerticalAlign.TOP,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
fieldKey: "center",
|
|
264
|
+
label: "По центру",
|
|
265
|
+
horizontalAlign: EHorizontalAlign.CENTER,
|
|
266
|
+
verticalAlign: EVerticalAlign.BOTTOM,
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
const data: ITableBasicRow[] = [
|
|
271
|
+
{
|
|
272
|
+
rowKey: "1",
|
|
273
|
+
rowData: {
|
|
274
|
+
left: "Выравнивание по верхнему краю ячейки. Текст занимает несколько строк, чтобы разница по вертикали была видна.",
|
|
275
|
+
center: "По нижнему краю ячейки",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
rowKey: "2",
|
|
280
|
+
rowData: {
|
|
281
|
+
left: "Ещё одна многострочная ячейка, задающая высоту строки таблицы для наглядности примера.",
|
|
282
|
+
center: "По нижнему краю ячейки",
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
export const Alignments = () => (
|
|
288
|
+
<MasterTable>
|
|
289
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
290
|
+
</MasterTable>
|
|
291
|
+
);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### CellTypes
|
|
295
|
+
|
|
296
|
+
`stories/TableBasic/examples/CellTypes.tsx`
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import React, { useState } from "react";
|
|
300
|
+
import {
|
|
301
|
+
Button,
|
|
302
|
+
Checkbox,
|
|
303
|
+
ECellType,
|
|
304
|
+
EButtonTheme,
|
|
305
|
+
EComponentSize,
|
|
306
|
+
ITableBasicColumn,
|
|
307
|
+
ITableBasicRow,
|
|
308
|
+
MasterTable,
|
|
309
|
+
} from "@sberbusiness/triplex-next";
|
|
310
|
+
|
|
311
|
+
const rows = [
|
|
312
|
+
{ rowKey: "1350", number: "1350", recipient: "ООО Ромашка" },
|
|
313
|
+
{ rowKey: "1351", number: "1351", recipient: "ИП Иванов И. И." },
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
// Колонки собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
317
|
+
const columns: ITableBasicColumn[] = [
|
|
318
|
+
{ fieldKey: "checkbox", cellType: ECellType.CHECKBOX, label: "", width: 48 },
|
|
319
|
+
{ fieldKey: "number", cellType: ECellType.TEXT, label: "Номер (text)", width: 120 },
|
|
320
|
+
{ fieldKey: "recipient", cellType: ECellType.TEXT, label: "Получатель (text)" },
|
|
321
|
+
{ fieldKey: "action", cellType: ECellType.COMPONENTS, label: "Действие (components)", width: 180 },
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
export const CellTypes = () => {
|
|
325
|
+
const [checkedRows, setCheckedRows] = useState<string[]>([]);
|
|
326
|
+
|
|
327
|
+
const toggleRow = (rowKey: string, checked: boolean) => {
|
|
328
|
+
setCheckedRows((prev) => (checked ? [...prev, rowKey] : prev.filter((key) => key !== rowKey)));
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const data: ITableBasicRow[] = rows.map((row) => ({
|
|
332
|
+
rowKey: row.rowKey,
|
|
333
|
+
selected: checkedRows.includes(row.rowKey),
|
|
334
|
+
rowData: {
|
|
335
|
+
checkbox: (
|
|
336
|
+
<Checkbox
|
|
337
|
+
checked={checkedRows.includes(row.rowKey)}
|
|
338
|
+
onChange={(event) => toggleRow(row.rowKey, event.target.checked)}
|
|
339
|
+
aria-label={`Выбрать строку ${row.number}`}
|
|
340
|
+
/>
|
|
341
|
+
),
|
|
342
|
+
number: row.number,
|
|
343
|
+
recipient: row.recipient,
|
|
344
|
+
action: (
|
|
345
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => {}}>
|
|
346
|
+
Напечатать
|
|
347
|
+
</Button>
|
|
348
|
+
),
|
|
349
|
+
},
|
|
350
|
+
}));
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<MasterTable>
|
|
354
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
355
|
+
</MasterTable>
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Sorting
|
|
361
|
+
|
|
362
|
+
`stories/TableBasic/examples/Sorting.tsx`
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import React, { useState } from "react";
|
|
366
|
+
import {
|
|
367
|
+
EHorizontalAlign,
|
|
368
|
+
EOrderDirection,
|
|
369
|
+
ISortOrder,
|
|
370
|
+
ITableBasicColumn,
|
|
371
|
+
ITableBasicRow,
|
|
372
|
+
MasterTable,
|
|
373
|
+
} from "@sberbusiness/triplex-next";
|
|
374
|
+
|
|
375
|
+
const rows = [
|
|
376
|
+
{ rowKey: "1350", number: "1350", recipient: "ООО Ромашка", sum: 9450 },
|
|
377
|
+
{ rowKey: "1351", number: "1351", recipient: "ИП Иванов И. И.", sum: 18914 },
|
|
378
|
+
{ rowKey: "1352", number: "1352", recipient: "ООО Росинка", sum: 4200 },
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
export const Sorting = () => {
|
|
382
|
+
// Направление переключается по циклу none → asc → desc → none, следующее значение приходит в onOrderBy.
|
|
383
|
+
const [order, setOrder] = useState<ISortOrder>({ fieldKey: "sum", direction: EOrderDirection.NONE });
|
|
384
|
+
|
|
385
|
+
const getDirection = (fieldKey: string) => (order.fieldKey === fieldKey ? order.direction : EOrderDirection.NONE);
|
|
386
|
+
|
|
387
|
+
const columns: ITableBasicColumn[] = [
|
|
388
|
+
{ fieldKey: "number", label: "Номер", width: 120, orderDirection: getDirection("number") },
|
|
389
|
+
{ fieldKey: "recipient", label: "Получатель (без сортировки)" },
|
|
390
|
+
{
|
|
391
|
+
fieldKey: "sum",
|
|
392
|
+
label: "Сумма",
|
|
393
|
+
width: 160,
|
|
394
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
395
|
+
orderDirection: getDirection("sum"),
|
|
396
|
+
},
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
const sortedRows = [...rows].sort((a, b) => {
|
|
400
|
+
if (order.direction === EOrderDirection.NONE) {
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const sign = order.direction === EOrderDirection.ASC ? 1 : -1;
|
|
405
|
+
|
|
406
|
+
return order.fieldKey === "sum" ? sign * (a.sum - b.sum) : sign * a.number.localeCompare(b.number);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const data: ITableBasicRow[] = sortedRows.map((row) => ({
|
|
410
|
+
rowKey: row.rowKey,
|
|
411
|
+
rowData: { number: row.number, recipient: row.recipient, sum: row.sum.toLocaleString("ru-RU") },
|
|
412
|
+
}));
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<MasterTable>
|
|
416
|
+
<MasterTable.TableBasic
|
|
417
|
+
columns={columns}
|
|
418
|
+
data={data}
|
|
419
|
+
onOrderBy={setOrder}
|
|
420
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
421
|
+
/>
|
|
422
|
+
</MasterTable>
|
|
423
|
+
);
|
|
424
|
+
};
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Headless
|
|
428
|
+
|
|
429
|
+
`stories/TableBasic/examples/Headless.tsx`
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import React from "react";
|
|
433
|
+
import { ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
434
|
+
|
|
435
|
+
const columns: ITableBasicColumn[] = [
|
|
436
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
437
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
438
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const data: ITableBasicRow[] = [
|
|
442
|
+
{ rowKey: "1350", rowData: { number: "1350", recipient: "ООО Ромашка", sum: "9 450,00" } },
|
|
443
|
+
{ rowKey: "1351", rowData: { number: "1351", recipient: "ИП Иванов И. И.", sum: "18 914,00" } },
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
export const Headless = () => (
|
|
447
|
+
<MasterTable>
|
|
448
|
+
<MasterTable.TableBasic headless columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
449
|
+
</MasterTable>
|
|
450
|
+
);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### HighlightRowOnHover
|
|
454
|
+
|
|
455
|
+
`stories/TableBasic/examples/HighlightRowOnHover.tsx`
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
import React from "react";
|
|
459
|
+
import { ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
460
|
+
|
|
461
|
+
const columns: ITableBasicColumn[] = [
|
|
462
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
463
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
464
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
const data: ITableBasicRow[] = [
|
|
468
|
+
{ rowKey: "1350", rowData: { number: "1350", recipient: "ООО Ромашка", sum: "9 450,00" } },
|
|
469
|
+
{ rowKey: "1351", rowData: { number: "1351", recipient: "ИП Иванов И. И.", sum: "18 914,00" } },
|
|
470
|
+
{ rowKey: "1352", rowData: { number: "1352", recipient: "ООО Росинка", sum: "28 392,00" } },
|
|
471
|
+
];
|
|
472
|
+
|
|
473
|
+
export const HighlightRowOnHover = () => (
|
|
474
|
+
<MasterTable>
|
|
475
|
+
<MasterTable.TableBasic
|
|
476
|
+
highlightRowOnHover
|
|
477
|
+
columns={columns}
|
|
478
|
+
data={data}
|
|
479
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
480
|
+
/>
|
|
481
|
+
</MasterTable>
|
|
482
|
+
);
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### ClickableRows
|
|
486
|
+
|
|
487
|
+
`stories/TableBasic/examples/ClickableRows.tsx`
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
import React, { useState } from "react";
|
|
491
|
+
import { ITableBasicColumn, ITableBasicRow, MasterTable } from "@sberbusiness/triplex-next";
|
|
492
|
+
|
|
493
|
+
const columns: ITableBasicColumn[] = [
|
|
494
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
495
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
496
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
const rows = [
|
|
500
|
+
{ rowKey: "1350", number: "1350", recipient: "ООО Ромашка", sum: "9 450,00" },
|
|
501
|
+
{ rowKey: "1351", number: "1351", recipient: "ИП Иванов И. И.", sum: "18 914,00" },
|
|
502
|
+
{ rowKey: "1352", number: "1352", recipient: "ООО Росинка", sum: "28 392,00" },
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
export const ClickableRows = () => {
|
|
506
|
+
const [selectedRowKey, setSelectedRowKey] = useState("1351");
|
|
507
|
+
|
|
508
|
+
// Заданный onClickRow сам включает подсветку строки при наведении, highlightRowOnHover не нужен.
|
|
509
|
+
const data: ITableBasicRow[] = rows.map((row) => ({
|
|
510
|
+
rowKey: row.rowKey,
|
|
511
|
+
selected: row.rowKey === selectedRowKey,
|
|
512
|
+
rowData: { number: row.number, recipient: row.recipient, sum: row.sum },
|
|
513
|
+
}));
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<MasterTable>
|
|
517
|
+
<MasterTable.TableBasic
|
|
518
|
+
columns={columns}
|
|
519
|
+
data={data}
|
|
520
|
+
onClickRow={setSelectedRowKey}
|
|
521
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
522
|
+
/>
|
|
523
|
+
</MasterTable>
|
|
524
|
+
);
|
|
525
|
+
};
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### NoData
|
|
529
|
+
|
|
530
|
+
`stories/TableBasic/examples/NoData.tsx`
|
|
531
|
+
|
|
532
|
+
```tsx
|
|
533
|
+
import React from "react";
|
|
534
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
535
|
+
import {
|
|
536
|
+
EFontType,
|
|
537
|
+
EFontWeightTitle,
|
|
538
|
+
ETextSize,
|
|
539
|
+
ETitleSize,
|
|
540
|
+
Gap,
|
|
541
|
+
ITableBasicColumn,
|
|
542
|
+
MasterTable,
|
|
543
|
+
Text,
|
|
544
|
+
Title,
|
|
545
|
+
} from "@sberbusiness/triplex-next";
|
|
546
|
+
|
|
547
|
+
const columns: ITableBasicColumn[] = [
|
|
548
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
549
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
550
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
const renderNoData = () => (
|
|
554
|
+
<>
|
|
555
|
+
<EmptytableSysIcon96 />
|
|
556
|
+
<Gap size={8} />
|
|
557
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
558
|
+
Пока нет документов
|
|
559
|
+
</Title>
|
|
560
|
+
<Gap size={12} />
|
|
561
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
562
|
+
Здесь появятся платёжные поручения, когда вы их создадите.
|
|
563
|
+
</Text>
|
|
564
|
+
</>
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
export const NoData = () => (
|
|
568
|
+
<MasterTable>
|
|
569
|
+
<MasterTable.TableBasic columns={columns} data={[]} renderNoData={renderNoData} />
|
|
570
|
+
</MasterTable>
|
|
571
|
+
);
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### NoColumns
|
|
575
|
+
|
|
576
|
+
`stories/TableBasic/examples/NoColumns.tsx`
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
import React, { useState } from "react";
|
|
580
|
+
import {
|
|
581
|
+
Button,
|
|
582
|
+
EButtonTheme,
|
|
583
|
+
EComponentSize,
|
|
584
|
+
EFontType,
|
|
585
|
+
EFontWeightTitle,
|
|
586
|
+
ETextSize,
|
|
587
|
+
ETitleSize,
|
|
588
|
+
Gap,
|
|
589
|
+
ITableBasicColumn,
|
|
590
|
+
ITableBasicRow,
|
|
591
|
+
MasterTable,
|
|
592
|
+
Text,
|
|
593
|
+
Title,
|
|
594
|
+
} from "@sberbusiness/triplex-next";
|
|
595
|
+
|
|
596
|
+
const defaultColumns: ITableBasicColumn[] = [
|
|
597
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
598
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
599
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
const data: ITableBasicRow[] = [
|
|
603
|
+
{ rowKey: "1350", rowData: { number: "1350", recipient: "ООО Ромашка", sum: "9 450,00" } },
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
export const NoColumns = () => {
|
|
607
|
+
// Все колонки скрыты: renderNoColumns вызывается вместо таблицы.
|
|
608
|
+
const [columns, setColumns] = useState<ITableBasicColumn[]>(
|
|
609
|
+
defaultColumns.map((column) => ({ ...column, hidden: true })),
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const renderNoColumns = () => (
|
|
613
|
+
<MasterTable.NoColumns>
|
|
614
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
615
|
+
Все колонки таблицы скрыты
|
|
616
|
+
</Title>
|
|
617
|
+
<Gap size={12} />
|
|
618
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
619
|
+
Выберите нужные вам для отображения колонки в настройках таблицы.
|
|
620
|
+
</Text>
|
|
621
|
+
<Gap size={24} />
|
|
622
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD} onClick={() => setColumns(defaultColumns)}>
|
|
623
|
+
Сбросить настройки
|
|
624
|
+
</Button>
|
|
625
|
+
</MasterTable.NoColumns>
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
return (
|
|
629
|
+
<MasterTable>
|
|
630
|
+
<MasterTable.TableBasic
|
|
631
|
+
columns={columns}
|
|
632
|
+
data={data}
|
|
633
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
634
|
+
renderNoColumns={renderNoColumns}
|
|
635
|
+
/>
|
|
636
|
+
</MasterTable>
|
|
637
|
+
);
|
|
638
|
+
};
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Loading
|
|
642
|
+
|
|
643
|
+
`stories/TableBasic/examples/Loading.tsx`
|
|
644
|
+
|
|
645
|
+
```tsx
|
|
646
|
+
import React from "react";
|
|
647
|
+
import { ETextSize, Gap, ITableBasicColumn, ITableBasicRow, MasterTable, Text } from "@sberbusiness/triplex-next";
|
|
648
|
+
|
|
649
|
+
const columns: ITableBasicColumn[] = [
|
|
650
|
+
{ fieldKey: "number", label: "Номер", width: 100 },
|
|
651
|
+
{ fieldKey: "recipient", label: "Получатель" },
|
|
652
|
+
{ fieldKey: "sum", label: "Сумма", width: 140 },
|
|
653
|
+
];
|
|
654
|
+
|
|
655
|
+
const data: ITableBasicRow[] = [
|
|
656
|
+
{ rowKey: "1350", rowData: { number: "1350", recipient: "ООО Ромашка", sum: "9 450,00" } },
|
|
657
|
+
{ rowKey: "1351", rowData: { number: "1351", recipient: "ИП Иванов И. И.", sum: "18 914,00" } },
|
|
658
|
+
];
|
|
659
|
+
|
|
660
|
+
// Состояние загрузки задаётся на MasterTable, TableBasic берёт его из контекста.
|
|
661
|
+
export const Loading = () => (
|
|
662
|
+
<>
|
|
663
|
+
<Text tag="div" size={ETextSize.B3}>
|
|
664
|
+
Загрузка поверх уже показанных данных
|
|
665
|
+
</Text>
|
|
666
|
+
<Gap size={12} />
|
|
667
|
+
<MasterTable loading>
|
|
668
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => <div>Нет данных</div>} />
|
|
669
|
+
</MasterTable>
|
|
670
|
+
<Gap size={24} />
|
|
671
|
+
<Text tag="div" size={ETextSize.B3}>
|
|
672
|
+
Загрузка при пустой таблице
|
|
673
|
+
</Text>
|
|
674
|
+
<Gap size={12} />
|
|
675
|
+
<MasterTable loading>
|
|
676
|
+
<MasterTable.TableBasic columns={columns} data={[]} renderNoData={() => <div>Нет данных</div>} />
|
|
677
|
+
</MasterTable>
|
|
678
|
+
</>
|
|
679
|
+
);
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### TableSpan
|
|
683
|
+
|
|
684
|
+
`stories/TableBasic/examples/TableSpan.tsx`
|
|
685
|
+
|
|
686
|
+
```tsx
|
|
687
|
+
import React from "react";
|
|
688
|
+
import {
|
|
689
|
+
Amount,
|
|
690
|
+
EHorizontalAlign,
|
|
691
|
+
EVerticalAlign,
|
|
692
|
+
MasterTable,
|
|
693
|
+
ITableBasicColumn,
|
|
694
|
+
ITableBasicRow,
|
|
695
|
+
} from "@sberbusiness/triplex-next";
|
|
696
|
+
|
|
697
|
+
// Колонки и данные собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
698
|
+
const backendData: ITableBasicRow[] = [
|
|
699
|
+
{
|
|
700
|
+
rowKey: "637",
|
|
701
|
+
rowData: {
|
|
702
|
+
number: "637",
|
|
703
|
+
sender: "ООО Ромашка",
|
|
704
|
+
sum: "1337,00",
|
|
705
|
+
status: "Ячейки первой и второй строки объединены по вертикали.",
|
|
706
|
+
},
|
|
707
|
+
rowLayout: {
|
|
708
|
+
status: { rowSpan: 2 },
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
rowKey: "638",
|
|
713
|
+
rowData: {
|
|
714
|
+
number: "638",
|
|
715
|
+
sender: "ООО Росинка",
|
|
716
|
+
sum: "420,00",
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
rowKey: "639",
|
|
721
|
+
rowData: {
|
|
722
|
+
number: "639",
|
|
723
|
+
sender: "Ячейки второго и третьего столбца объединены по горизонтали.",
|
|
724
|
+
status: "Исполнено",
|
|
725
|
+
},
|
|
726
|
+
rowLayout: {
|
|
727
|
+
sender: { colSpan: 2 },
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
];
|
|
731
|
+
|
|
732
|
+
const columns: ITableBasicColumn[] = [
|
|
733
|
+
{
|
|
734
|
+
fieldKey: "number",
|
|
735
|
+
label: "Номер",
|
|
736
|
+
width: 65,
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
fieldKey: "sender",
|
|
740
|
+
label: "Отправитель",
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
fieldKey: "sum",
|
|
744
|
+
label: "Сумма",
|
|
745
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
746
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency={"RUB"} />,
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
fieldKey: "status",
|
|
750
|
+
label: "Статус",
|
|
751
|
+
verticalAlign: EVerticalAlign.MIDDLE,
|
|
752
|
+
width: 150,
|
|
753
|
+
},
|
|
754
|
+
];
|
|
755
|
+
|
|
756
|
+
export const TableSpan = () => (
|
|
757
|
+
<div style={{ maxWidth: "600px" }}>
|
|
758
|
+
<MasterTable>
|
|
759
|
+
<MasterTable.TableBasic columns={columns} data={backendData} renderNoData={() => <div>Нет данных</div>} />
|
|
760
|
+
</MasterTable>
|
|
761
|
+
</div>
|
|
762
|
+
);
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### TableSettingsColumn
|
|
766
|
+
|
|
767
|
+
`stories/TableBasic/examples/TableSettingsColumn.tsx`
|
|
768
|
+
|
|
769
|
+
```tsx
|
|
770
|
+
import React, { useState } from "react";
|
|
771
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
772
|
+
import {
|
|
773
|
+
Amount,
|
|
774
|
+
Button,
|
|
775
|
+
Checkbox,
|
|
776
|
+
ColumnSettings,
|
|
777
|
+
EButtonTheme,
|
|
778
|
+
EComponentSize,
|
|
779
|
+
EFontType,
|
|
780
|
+
EFontWeightTitle,
|
|
781
|
+
EHorizontalAlign,
|
|
782
|
+
ETextSize,
|
|
783
|
+
ETitleSize,
|
|
784
|
+
Gap,
|
|
785
|
+
MasterTable,
|
|
786
|
+
Text,
|
|
787
|
+
Title,
|
|
788
|
+
ITableBasicColumn,
|
|
789
|
+
ITableBasicRow,
|
|
790
|
+
} from "@sberbusiness/triplex-next";
|
|
791
|
+
|
|
792
|
+
const renderCounterpartyDetails = (purpose: string, account: string, tax: string) => (
|
|
793
|
+
<>
|
|
794
|
+
<div>
|
|
795
|
+
{purpose}
|
|
796
|
+
<br />
|
|
797
|
+
{account}
|
|
798
|
+
</div>
|
|
799
|
+
<Gap size={4} />
|
|
800
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
801
|
+
{tax}
|
|
802
|
+
</Text>
|
|
803
|
+
</>
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
const renderNoData = () => (
|
|
807
|
+
<>
|
|
808
|
+
<EmptytableSysIcon96 />
|
|
809
|
+
<Gap size={8} />
|
|
810
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
811
|
+
Текст заголовка
|
|
812
|
+
</Title>
|
|
813
|
+
<Gap size={12} />
|
|
814
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
815
|
+
Нет данных, но можно предложить какие-то действия для заполнения таблицы
|
|
816
|
+
</Text>
|
|
817
|
+
<Gap size={24} />
|
|
818
|
+
<div>
|
|
819
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
820
|
+
Button text
|
|
821
|
+
</Button>
|
|
822
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
823
|
+
Button text
|
|
824
|
+
</Button>
|
|
825
|
+
</div>
|
|
826
|
+
</>
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
const renderNoColumns = (onReset: () => void) => (
|
|
830
|
+
<MasterTable.NoColumns>
|
|
831
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
832
|
+
Все колонки таблицы скрыты
|
|
833
|
+
</Title>
|
|
834
|
+
<Gap size={12} />
|
|
835
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
836
|
+
Выберите нужные вам для отображения колонки в настройках таблицы.
|
|
837
|
+
</Text>
|
|
838
|
+
<Gap size={24} />
|
|
839
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD} onClick={onReset}>
|
|
840
|
+
Сбросить настройки
|
|
841
|
+
</Button>
|
|
842
|
+
</MasterTable.NoColumns>
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
// Колонки и данные собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
846
|
+
const defaultColumns: ITableBasicColumn[] = [
|
|
847
|
+
{
|
|
848
|
+
fieldKey: "number",
|
|
849
|
+
label: "Номер",
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
fieldKey: "value",
|
|
853
|
+
label: "Получатель",
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
fieldKey: "sum",
|
|
857
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
858
|
+
label: "Сумма",
|
|
859
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
fieldKey: "status",
|
|
863
|
+
label: "Статус",
|
|
864
|
+
},
|
|
865
|
+
];
|
|
866
|
+
|
|
867
|
+
const data: ITableBasicRow[] = Array.from({ length: 5 }, (_, index) => ({
|
|
868
|
+
rowData: {
|
|
869
|
+
number: 1397450 + index,
|
|
870
|
+
status: "Исполнено",
|
|
871
|
+
sum: "1220000000",
|
|
872
|
+
value: renderCounterpartyDetails(
|
|
873
|
+
"Платежное поручение ООО Ромашка",
|
|
874
|
+
"40702 810 2 0527 5000000",
|
|
875
|
+
"В том числе НДС 20%",
|
|
876
|
+
),
|
|
877
|
+
},
|
|
878
|
+
rowKey: `table-basic-row-${index}`,
|
|
879
|
+
}));
|
|
880
|
+
|
|
881
|
+
export const TableSettingsColumn = () => {
|
|
882
|
+
const [columns, setColumns] = useState(defaultColumns);
|
|
883
|
+
const [settingsDropdownOpened, setSettingsDropdownOpened] = useState(false);
|
|
884
|
+
|
|
885
|
+
const resetColumns = () => {
|
|
886
|
+
setColumns(defaultColumns);
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
const showAllColumns = () => {
|
|
890
|
+
setColumns(
|
|
891
|
+
columns.map((column) => {
|
|
892
|
+
if (column.hidden) {
|
|
893
|
+
return { ...column, hidden: false };
|
|
894
|
+
}
|
|
895
|
+
return column;
|
|
896
|
+
}),
|
|
897
|
+
);
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
const toggleColumn = (fieldKey: string, checked: boolean) => {
|
|
901
|
+
setColumns(
|
|
902
|
+
columns.map((column) => {
|
|
903
|
+
if (column.fieldKey === fieldKey) {
|
|
904
|
+
return { ...column, hidden: !checked };
|
|
905
|
+
}
|
|
906
|
+
return column;
|
|
907
|
+
}),
|
|
908
|
+
);
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
const renderColumns = () => (
|
|
912
|
+
<ColumnSettings.SortableList columns={columns} onColumnsChange={setColumns}>
|
|
913
|
+
{columns.map((column) => (
|
|
914
|
+
<ColumnSettings.SortableList.Item key={column.fieldKey} id={column.fieldKey}>
|
|
915
|
+
<Checkbox
|
|
916
|
+
checked={column.hidden !== true}
|
|
917
|
+
onChange={(event) => toggleColumn(column.fieldKey, event.target.checked)}
|
|
918
|
+
>
|
|
919
|
+
{column.label}
|
|
920
|
+
</Checkbox>
|
|
921
|
+
</ColumnSettings.SortableList.Item>
|
|
922
|
+
))}
|
|
923
|
+
</ColumnSettings.SortableList>
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
return (
|
|
927
|
+
<MasterTable>
|
|
928
|
+
<MasterTable.ChipPanel>
|
|
929
|
+
<MasterTable.ChipPanel.Links>
|
|
930
|
+
<MasterTable.TableBasicSettings
|
|
931
|
+
linkTitle={settingsDropdownOpened ? "Скрыть настройки" : "Настройки"}
|
|
932
|
+
opened={settingsDropdownOpened}
|
|
933
|
+
setOpened={setSettingsDropdownOpened}
|
|
934
|
+
>
|
|
935
|
+
<MasterTable.TableBasicSettings.Header>Показать столбцы</MasterTable.TableBasicSettings.Header>
|
|
936
|
+
|
|
937
|
+
<MasterTable.TableBasicSettings.Body>
|
|
938
|
+
<ColumnSettings>{renderColumns()}</ColumnSettings>
|
|
939
|
+
</MasterTable.TableBasicSettings.Body>
|
|
940
|
+
|
|
941
|
+
<MasterTable.TableBasicSettings.Footer>
|
|
942
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={showAllColumns}>
|
|
943
|
+
Выбрать все
|
|
944
|
+
</Button>
|
|
945
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={resetColumns}>
|
|
946
|
+
Сбросить настройки
|
|
947
|
+
</Button>
|
|
948
|
+
</MasterTable.TableBasicSettings.Footer>
|
|
949
|
+
</MasterTable.TableBasicSettings>
|
|
950
|
+
</MasterTable.ChipPanel.Links>
|
|
951
|
+
</MasterTable.ChipPanel>
|
|
952
|
+
<MasterTable.TableBasic
|
|
953
|
+
columns={columns}
|
|
954
|
+
data={data}
|
|
955
|
+
renderNoData={renderNoData}
|
|
956
|
+
renderNoColumns={() => renderNoColumns(resetColumns)}
|
|
957
|
+
/>
|
|
958
|
+
</MasterTable>
|
|
959
|
+
);
|
|
960
|
+
};
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
### TableSettingsColumnExtended
|
|
964
|
+
|
|
965
|
+
`stories/TableBasic/examples/TableSettingsColumnExtended.tsx`
|
|
966
|
+
|
|
967
|
+
```tsx
|
|
968
|
+
import React, { useState } from "react";
|
|
969
|
+
import { EmptytableSysIcon96 } from "@sberbusiness/icons-next";
|
|
970
|
+
import {
|
|
971
|
+
Amount,
|
|
972
|
+
Button,
|
|
973
|
+
Checkbox,
|
|
974
|
+
ColumnSettings,
|
|
975
|
+
EButtonTheme,
|
|
976
|
+
EComponentSize,
|
|
977
|
+
EFontType,
|
|
978
|
+
EFontWeightTitle,
|
|
979
|
+
EHorizontalAlign,
|
|
980
|
+
ETextSize,
|
|
981
|
+
ETitleSize,
|
|
982
|
+
Gap,
|
|
983
|
+
MasterTable,
|
|
984
|
+
Text,
|
|
985
|
+
Title,
|
|
986
|
+
ITableBasicColumn,
|
|
987
|
+
ITableBasicRow,
|
|
988
|
+
} from "@sberbusiness/triplex-next";
|
|
989
|
+
|
|
990
|
+
const renderNoData = () => (
|
|
991
|
+
<>
|
|
992
|
+
<EmptytableSysIcon96 />
|
|
993
|
+
<Gap size={8} />
|
|
994
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
995
|
+
Текст заголовка
|
|
996
|
+
</Title>
|
|
997
|
+
<Gap size={12} />
|
|
998
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
999
|
+
Нет данных, но можно предложить какие-то действия для заполнения таблицы
|
|
1000
|
+
</Text>
|
|
1001
|
+
<Gap size={24} />
|
|
1002
|
+
<div>
|
|
1003
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
1004
|
+
Button text
|
|
1005
|
+
</Button>
|
|
1006
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
1007
|
+
Button text
|
|
1008
|
+
</Button>
|
|
1009
|
+
</div>
|
|
1010
|
+
</>
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
const renderNoColumns = (onReset: () => void) => (
|
|
1014
|
+
<MasterTable.NoColumns>
|
|
1015
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
1016
|
+
Все колонки таблицы скрыты
|
|
1017
|
+
</Title>
|
|
1018
|
+
<Gap size={12} />
|
|
1019
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
1020
|
+
Выберите нужные вам для отображения колонки в настройках таблицы.
|
|
1021
|
+
</Text>
|
|
1022
|
+
<Gap size={24} />
|
|
1023
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD} onClick={onReset}>
|
|
1024
|
+
Сбросить настройки
|
|
1025
|
+
</Button>
|
|
1026
|
+
</MasterTable.NoColumns>
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
// Колонки собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
1030
|
+
const defaultColumns: ITableBasicColumn[] = [
|
|
1031
|
+
{
|
|
1032
|
+
fieldKey: "number",
|
|
1033
|
+
label: "Номер",
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
fieldKey: "value",
|
|
1037
|
+
label: "Получатель",
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
fieldKey: "sum",
|
|
1041
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
1042
|
+
label: "Сумма",
|
|
1043
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
fieldKey: "status",
|
|
1047
|
+
label: "Статус",
|
|
1048
|
+
},
|
|
1049
|
+
];
|
|
1050
|
+
|
|
1051
|
+
interface IValueColumnOptions {
|
|
1052
|
+
showCounterparty: boolean;
|
|
1053
|
+
showNumber: boolean;
|
|
1054
|
+
showNDS: boolean;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const initiateValueColumnOptions = (defaultChecked: boolean): IValueColumnOptions => ({
|
|
1058
|
+
showCounterparty: defaultChecked,
|
|
1059
|
+
showNumber: defaultChecked,
|
|
1060
|
+
showNDS: defaultChecked,
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
const defaultValueColumnOptions = initiateValueColumnOptions(true);
|
|
1064
|
+
|
|
1065
|
+
const mapValueColumnOptionKeyToName: Record<string, string> = {
|
|
1066
|
+
showCounterparty: "Получатель",
|
|
1067
|
+
showNumber: "Номер счета",
|
|
1068
|
+
showNDS: "НДС",
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Возвращает данные для таблицы.
|
|
1073
|
+
* @param showCounterparty - показать блок Получатель в столбце Значение.
|
|
1074
|
+
* @param showNumber - показать блок Номер счета в столбце Значение.
|
|
1075
|
+
* @param showNDS - показать блок НДС счета в столбце Значение.
|
|
1076
|
+
*/
|
|
1077
|
+
const getData = ({ showCounterparty, showNumber, showNDS }: IValueColumnOptions): ITableBasicRow[] =>
|
|
1078
|
+
Array.from({ length: 5 }, (_, index) => ({
|
|
1079
|
+
rowData: {
|
|
1080
|
+
number: 1397450 + index,
|
|
1081
|
+
status: "Исполнено",
|
|
1082
|
+
sum: "1220000000",
|
|
1083
|
+
value: (
|
|
1084
|
+
<>
|
|
1085
|
+
{(showCounterparty || showNumber) && (
|
|
1086
|
+
<>
|
|
1087
|
+
<div>
|
|
1088
|
+
{showCounterparty && (
|
|
1089
|
+
<>
|
|
1090
|
+
Платежное поручение ООО Ромашка <br />
|
|
1091
|
+
</>
|
|
1092
|
+
)}
|
|
1093
|
+
{showNumber && "40702 810 2 0527 5000000"}
|
|
1094
|
+
</div>
|
|
1095
|
+
<Gap size={4} />
|
|
1096
|
+
</>
|
|
1097
|
+
)}
|
|
1098
|
+
|
|
1099
|
+
{showNDS && (
|
|
1100
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
1101
|
+
В том числе НДС 20%
|
|
1102
|
+
</Text>
|
|
1103
|
+
)}
|
|
1104
|
+
</>
|
|
1105
|
+
),
|
|
1106
|
+
},
|
|
1107
|
+
rowKey: `table-basic-row-${index}`,
|
|
1108
|
+
}));
|
|
1109
|
+
|
|
1110
|
+
interface IValueColumnSettingsProps {
|
|
1111
|
+
column: ITableBasicColumn;
|
|
1112
|
+
options: IValueColumnOptions;
|
|
1113
|
+
setColumn: (checked: boolean) => void;
|
|
1114
|
+
setOptions: (option: IValueColumnOptions) => void;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const ValueColumnSettings = ({ column, options, setColumn, setOptions }: IValueColumnSettingsProps) => {
|
|
1118
|
+
const handleChildCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>, columnKey: string) => {
|
|
1119
|
+
const newOptions = { ...options, [columnKey]: event.target.checked };
|
|
1120
|
+
|
|
1121
|
+
if (event.target.checked !== !column.hidden) {
|
|
1122
|
+
if (event.target.checked) {
|
|
1123
|
+
if (Object.values(options).every((value) => value === false)) {
|
|
1124
|
+
setColumn(event.target.checked);
|
|
1125
|
+
}
|
|
1126
|
+
} else {
|
|
1127
|
+
if (Object.values(newOptions).every((value) => value === false)) {
|
|
1128
|
+
setColumn(event.target.checked);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
setOptions(newOptions);
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
const handleParentCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
1137
|
+
setOptions(initiateValueColumnOptions(event.target.checked));
|
|
1138
|
+
setColumn(event.target.checked);
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
const renderOptions = () => (
|
|
1142
|
+
<ColumnSettings.StaticList depth={1}>
|
|
1143
|
+
{Object.entries(options).map(([key, value], index) => (
|
|
1144
|
+
<ColumnSettings.StaticList.Item key={index}>
|
|
1145
|
+
<Checkbox checked={Boolean(value)} onChange={(event) => handleChildCheckboxChange(event, key)}>
|
|
1146
|
+
{mapValueColumnOptionKeyToName[key]}
|
|
1147
|
+
</Checkbox>
|
|
1148
|
+
</ColumnSettings.StaticList.Item>
|
|
1149
|
+
))}
|
|
1150
|
+
</ColumnSettings.StaticList>
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
return (
|
|
1154
|
+
<ColumnSettings.SortableList.Item id={column.fieldKey} staticContent={renderOptions()}>
|
|
1155
|
+
<Checkbox
|
|
1156
|
+
checked={!column.hidden}
|
|
1157
|
+
bulk={Object.values(options).some((value) => value !== !column.hidden)}
|
|
1158
|
+
onChange={handleParentCheckboxChange}
|
|
1159
|
+
>
|
|
1160
|
+
{column.label}
|
|
1161
|
+
</Checkbox>
|
|
1162
|
+
</ColumnSettings.SortableList.Item>
|
|
1163
|
+
);
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
export const TableSettingsColumnExtended = () => {
|
|
1167
|
+
const [columns, setColumns] = useState(defaultColumns);
|
|
1168
|
+
const [valueColumnOptions, setValueColumnOptions] = useState(defaultValueColumnOptions);
|
|
1169
|
+
const [settingsDropdownOpened, setSettingsDropdownOpened] = useState(false);
|
|
1170
|
+
|
|
1171
|
+
// Содержимое колонки «Получатель» выводится из настроек — отдельного состояния для данных не нужно.
|
|
1172
|
+
const data = getData(valueColumnOptions);
|
|
1173
|
+
|
|
1174
|
+
const resetColumns = () => {
|
|
1175
|
+
setColumns(defaultColumns);
|
|
1176
|
+
setValueColumnOptions(defaultValueColumnOptions);
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
const showAllColumns = () => {
|
|
1180
|
+
setColumns(
|
|
1181
|
+
columns.map((column) => {
|
|
1182
|
+
if (column.hidden) {
|
|
1183
|
+
return { ...column, hidden: false };
|
|
1184
|
+
}
|
|
1185
|
+
return column;
|
|
1186
|
+
}),
|
|
1187
|
+
);
|
|
1188
|
+
setValueColumnOptions(defaultValueColumnOptions);
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
const toggleColumn = (fieldKey: string, checked: boolean): void => {
|
|
1192
|
+
setColumns(
|
|
1193
|
+
columns.map((column) => {
|
|
1194
|
+
if (column.fieldKey === fieldKey) {
|
|
1195
|
+
return { ...column, hidden: !checked };
|
|
1196
|
+
}
|
|
1197
|
+
return column;
|
|
1198
|
+
}),
|
|
1199
|
+
);
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
const renderColumns = () => (
|
|
1203
|
+
<ColumnSettings.SortableList columns={columns} onColumnsChange={setColumns}>
|
|
1204
|
+
{columns.map((column) => {
|
|
1205
|
+
if (column.fieldKey === "value") {
|
|
1206
|
+
return (
|
|
1207
|
+
<ValueColumnSettings
|
|
1208
|
+
key={column.fieldKey}
|
|
1209
|
+
column={column}
|
|
1210
|
+
options={valueColumnOptions}
|
|
1211
|
+
setColumn={(checked: boolean) => toggleColumn(column.fieldKey, checked)}
|
|
1212
|
+
setOptions={(options) => {
|
|
1213
|
+
setValueColumnOptions(options);
|
|
1214
|
+
}}
|
|
1215
|
+
/>
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return (
|
|
1220
|
+
<ColumnSettings.SortableList.Item key={column.fieldKey} id={column.fieldKey}>
|
|
1221
|
+
<Checkbox
|
|
1222
|
+
checked={column.hidden !== true}
|
|
1223
|
+
onChange={(event) => toggleColumn(column.fieldKey, event.target.checked)}
|
|
1224
|
+
>
|
|
1225
|
+
{column.label}
|
|
1226
|
+
</Checkbox>
|
|
1227
|
+
</ColumnSettings.SortableList.Item>
|
|
1228
|
+
);
|
|
1229
|
+
})}
|
|
1230
|
+
</ColumnSettings.SortableList>
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
return (
|
|
1234
|
+
<MasterTable>
|
|
1235
|
+
<MasterTable.ChipPanel>
|
|
1236
|
+
<MasterTable.ChipPanel.Links>
|
|
1237
|
+
<MasterTable.TableBasicSettings
|
|
1238
|
+
linkTitle={settingsDropdownOpened ? "Скрыть настройки" : "Настройки"}
|
|
1239
|
+
opened={settingsDropdownOpened}
|
|
1240
|
+
setOpened={setSettingsDropdownOpened}
|
|
1241
|
+
>
|
|
1242
|
+
<MasterTable.TableBasicSettings.Header>Показать столбцы</MasterTable.TableBasicSettings.Header>
|
|
1243
|
+
|
|
1244
|
+
<MasterTable.TableBasicSettings.Body>
|
|
1245
|
+
<ColumnSettings>{renderColumns()}</ColumnSettings>
|
|
1246
|
+
</MasterTable.TableBasicSettings.Body>
|
|
1247
|
+
|
|
1248
|
+
<MasterTable.TableBasicSettings.Footer>
|
|
1249
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={showAllColumns}>
|
|
1250
|
+
Выбрать все
|
|
1251
|
+
</Button>
|
|
1252
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={resetColumns}>
|
|
1253
|
+
Сбросить настройки
|
|
1254
|
+
</Button>
|
|
1255
|
+
</MasterTable.TableBasicSettings.Footer>
|
|
1256
|
+
</MasterTable.TableBasicSettings>
|
|
1257
|
+
</MasterTable.ChipPanel.Links>
|
|
1258
|
+
</MasterTable.ChipPanel>
|
|
1259
|
+
<MasterTable.TableBasic
|
|
1260
|
+
columns={columns}
|
|
1261
|
+
data={data}
|
|
1262
|
+
renderNoData={renderNoData}
|
|
1263
|
+
renderNoColumns={() => renderNoColumns(resetColumns)}
|
|
1264
|
+
/>
|
|
1265
|
+
</MasterTable>
|
|
1266
|
+
);
|
|
1267
|
+
};
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
### TableWithPagination
|
|
1271
|
+
|
|
1272
|
+
`stories/TableBasic/examples/TableWithPagination.tsx`
|
|
1273
|
+
|
|
1274
|
+
```tsx
|
|
1275
|
+
import React, { useState } from "react";
|
|
1276
|
+
import {
|
|
1277
|
+
Amount,
|
|
1278
|
+
EFontType,
|
|
1279
|
+
EHorizontalAlign,
|
|
1280
|
+
ETextSize,
|
|
1281
|
+
Gap,
|
|
1282
|
+
MasterTable,
|
|
1283
|
+
Pagination,
|
|
1284
|
+
Text,
|
|
1285
|
+
ISelectExtendedFieldDefaultOption,
|
|
1286
|
+
ITableBasicColumn,
|
|
1287
|
+
ITableBasicRow,
|
|
1288
|
+
} from "@sberbusiness/triplex-next";
|
|
1289
|
+
|
|
1290
|
+
const renderCounterpartyDetails = (purpose: string, account: string, tax: string) => (
|
|
1291
|
+
<>
|
|
1292
|
+
<div>
|
|
1293
|
+
{purpose}
|
|
1294
|
+
<br />
|
|
1295
|
+
{account}
|
|
1296
|
+
</div>
|
|
1297
|
+
<Gap size={4} />
|
|
1298
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
1299
|
+
{tax}
|
|
1300
|
+
</Text>
|
|
1301
|
+
</>
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
// Колонки и данные собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
1305
|
+
const columns: ITableBasicColumn[] = [
|
|
1306
|
+
{
|
|
1307
|
+
fieldKey: "number",
|
|
1308
|
+
label: "Номер",
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
fieldKey: "value",
|
|
1312
|
+
label: "Получатель",
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
fieldKey: "sum",
|
|
1316
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
1317
|
+
label: "Сумма",
|
|
1318
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
fieldKey: "status",
|
|
1322
|
+
label: "Статус",
|
|
1323
|
+
},
|
|
1324
|
+
];
|
|
1325
|
+
|
|
1326
|
+
const data: ITableBasicRow[] = Array.from({ length: 300 }, (_, index) => ({
|
|
1327
|
+
rowData: {
|
|
1328
|
+
number: 1000 + index,
|
|
1329
|
+
status: "Исполнено",
|
|
1330
|
+
sum: "1220000000",
|
|
1331
|
+
value: renderCounterpartyDetails(
|
|
1332
|
+
"Платежное поручение ООО Ромашка",
|
|
1333
|
+
"40702 810 2 0527 5000000",
|
|
1334
|
+
"В том числе НДС 20%",
|
|
1335
|
+
),
|
|
1336
|
+
},
|
|
1337
|
+
rowKey: `table-basic-row-${index}`,
|
|
1338
|
+
}));
|
|
1339
|
+
|
|
1340
|
+
const options: ISelectExtendedFieldDefaultOption[] = [
|
|
1341
|
+
{ id: "0", value: "10", label: "10" },
|
|
1342
|
+
{ id: "1", value: "20", label: "20" },
|
|
1343
|
+
{ id: "2", value: "50", label: "50" },
|
|
1344
|
+
{ id: "3", value: "100", label: "100" },
|
|
1345
|
+
];
|
|
1346
|
+
|
|
1347
|
+
export const TableWithPagination = () => {
|
|
1348
|
+
const [page, setPage] = useState(1);
|
|
1349
|
+
const [pageSize, setPageSize] = useState(10);
|
|
1350
|
+
const totalItems = data.length;
|
|
1351
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
1352
|
+
|
|
1353
|
+
const handlePageSizeChange = (option: ISelectExtendedFieldDefaultOption) => {
|
|
1354
|
+
setPage(1);
|
|
1355
|
+
setPageSize(Number(option.value));
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
const selectedOption = options.find((option) => option.value === String(pageSize));
|
|
1359
|
+
|
|
1360
|
+
const getPaginatedData = () => {
|
|
1361
|
+
return data.slice((page - 1) * pageSize, page * pageSize);
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
return (
|
|
1365
|
+
<MasterTable>
|
|
1366
|
+
<MasterTable.TableBasic
|
|
1367
|
+
columns={columns}
|
|
1368
|
+
data={getPaginatedData()}
|
|
1369
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
1370
|
+
/>
|
|
1371
|
+
<MasterTable.PaginationPanel>
|
|
1372
|
+
<Pagination
|
|
1373
|
+
paginationNavigationProps={{
|
|
1374
|
+
currentPage: page,
|
|
1375
|
+
totalPages,
|
|
1376
|
+
boundaryCount: 1,
|
|
1377
|
+
siblingCount: 1,
|
|
1378
|
+
onCurrentPageChange: setPage,
|
|
1379
|
+
}}
|
|
1380
|
+
paginationSelectProps={{
|
|
1381
|
+
paginationLabel: "Показать на странице:",
|
|
1382
|
+
options,
|
|
1383
|
+
value: selectedOption || options[0],
|
|
1384
|
+
onChange: handlePageSizeChange,
|
|
1385
|
+
targetProps: {
|
|
1386
|
+
fieldLabel: "",
|
|
1387
|
+
},
|
|
1388
|
+
}}
|
|
1389
|
+
/>
|
|
1390
|
+
</MasterTable.PaginationPanel>
|
|
1391
|
+
</MasterTable>
|
|
1392
|
+
);
|
|
1393
|
+
};
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
### TableWithPaginationLoading
|
|
1397
|
+
|
|
1398
|
+
`stories/TableBasic/examples/TableWithPaginationLoading.tsx`
|
|
1399
|
+
|
|
1400
|
+
```tsx
|
|
1401
|
+
import React, { useState } from "react";
|
|
1402
|
+
import { EmptytableSysIcon96, NotfoundSysIcon96 } from "@sberbusiness/icons-next";
|
|
1403
|
+
import {
|
|
1404
|
+
Amount,
|
|
1405
|
+
Button,
|
|
1406
|
+
Col,
|
|
1407
|
+
EButtonTheme,
|
|
1408
|
+
EComponentSize,
|
|
1409
|
+
EFontType,
|
|
1410
|
+
EFontWeightTitle,
|
|
1411
|
+
EHorizontalAlign,
|
|
1412
|
+
ETextSize,
|
|
1413
|
+
ETitleSize,
|
|
1414
|
+
Gap,
|
|
1415
|
+
MasterTable,
|
|
1416
|
+
Pagination,
|
|
1417
|
+
Row,
|
|
1418
|
+
Text,
|
|
1419
|
+
TextField,
|
|
1420
|
+
Title,
|
|
1421
|
+
ISelectExtendedFieldDefaultOption,
|
|
1422
|
+
ITableBasicColumn,
|
|
1423
|
+
ITableBasicRow,
|
|
1424
|
+
} from "@sberbusiness/triplex-next";
|
|
1425
|
+
|
|
1426
|
+
const renderCounterpartyDetails = (purpose: string, account: string, tax: string) => (
|
|
1427
|
+
<>
|
|
1428
|
+
<div>
|
|
1429
|
+
{purpose}
|
|
1430
|
+
<br />
|
|
1431
|
+
{account}
|
|
1432
|
+
</div>
|
|
1433
|
+
<Gap size={4} />
|
|
1434
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
1435
|
+
{tax}
|
|
1436
|
+
</Text>
|
|
1437
|
+
</>
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
const renderNoData = (isFiltered: boolean) => (
|
|
1441
|
+
<>
|
|
1442
|
+
{isFiltered ? <NotfoundSysIcon96 /> : <EmptytableSysIcon96 />}
|
|
1443
|
+
<Gap size={8} />
|
|
1444
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
1445
|
+
Текст заголовка
|
|
1446
|
+
</Title>
|
|
1447
|
+
<Gap size={12} />
|
|
1448
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
1449
|
+
Нет данных, но можно предложить какие-то действия для заполнения таблицы
|
|
1450
|
+
</Text>
|
|
1451
|
+
<Gap size={24} />
|
|
1452
|
+
<div>
|
|
1453
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
1454
|
+
Button text
|
|
1455
|
+
</Button>
|
|
1456
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
1457
|
+
Button text
|
|
1458
|
+
</Button>
|
|
1459
|
+
</div>
|
|
1460
|
+
</>
|
|
1461
|
+
);
|
|
1462
|
+
|
|
1463
|
+
// Колонки собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
1464
|
+
const columns: ITableBasicColumn[] = [
|
|
1465
|
+
{
|
|
1466
|
+
fieldKey: "number",
|
|
1467
|
+
label: "Номер",
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
fieldKey: "value",
|
|
1471
|
+
label: "Получатель",
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
fieldKey: "sum",
|
|
1475
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
1476
|
+
label: "Сумма",
|
|
1477
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
fieldKey: "status",
|
|
1481
|
+
label: "Статус",
|
|
1482
|
+
},
|
|
1483
|
+
];
|
|
1484
|
+
|
|
1485
|
+
const filtersAndPaginationRows = Array.from({ length: 30 }, (_, index) => ({
|
|
1486
|
+
docNumber: String(1000 + index),
|
|
1487
|
+
recipient: index % 2 === 0 ? "ООО Ромашка" : "ИП Иванов Иван Иванович",
|
|
1488
|
+
status: ["Создан", "Подписан", "Оплачен"][index % 3],
|
|
1489
|
+
sum: String((index + 1) * 10000),
|
|
1490
|
+
}));
|
|
1491
|
+
|
|
1492
|
+
const pageSizeOptions: ISelectExtendedFieldDefaultOption[] = [
|
|
1493
|
+
{ id: "0", value: "10", label: "10" },
|
|
1494
|
+
{ id: "1", value: "20", label: "20" },
|
|
1495
|
+
{ id: "2", value: "50", label: "50" },
|
|
1496
|
+
];
|
|
1497
|
+
|
|
1498
|
+
export const TableWithPaginationLoading = () => {
|
|
1499
|
+
const [docNumberFilter, setDocNumberFilter] = useState("");
|
|
1500
|
+
const [recipientFilter, setRecipientFilter] = useState("");
|
|
1501
|
+
const [page, setPage] = useState(1);
|
|
1502
|
+
const [pageSize, setPageSize] = useState(10);
|
|
1503
|
+
|
|
1504
|
+
const isFiltered = docNumberFilter !== "" || recipientFilter !== "";
|
|
1505
|
+
|
|
1506
|
+
const filteredRows = filtersAndPaginationRows.filter(
|
|
1507
|
+
(row) =>
|
|
1508
|
+
row.docNumber.includes(docNumberFilter) &&
|
|
1509
|
+
row.recipient.toLowerCase().includes(recipientFilter.toLowerCase()),
|
|
1510
|
+
);
|
|
1511
|
+
|
|
1512
|
+
const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize));
|
|
1513
|
+
// Фильтр мог сократить выборку сильнее, чем текущая страница — страховка от выхода за диапазон.
|
|
1514
|
+
const currentPage = Math.min(page, totalPages);
|
|
1515
|
+
|
|
1516
|
+
const data: ITableBasicRow[] = filteredRows
|
|
1517
|
+
.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
|
1518
|
+
.map((row) => ({
|
|
1519
|
+
rowData: {
|
|
1520
|
+
number: row.docNumber,
|
|
1521
|
+
status: row.status,
|
|
1522
|
+
sum: row.sum,
|
|
1523
|
+
value: renderCounterpartyDetails(
|
|
1524
|
+
`Платежное поручение ${row.recipient}`,
|
|
1525
|
+
"40702 810 2 0527 5000000",
|
|
1526
|
+
"В том числе НДС 20%",
|
|
1527
|
+
),
|
|
1528
|
+
},
|
|
1529
|
+
rowKey: `table-with-filters-row-${row.docNumber}`,
|
|
1530
|
+
}));
|
|
1531
|
+
|
|
1532
|
+
const handleChangeDocNumber = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
1533
|
+
setDocNumberFilter(event.target.value);
|
|
1534
|
+
setPage(1);
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
const handleChangeRecipient = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
1538
|
+
setRecipientFilter(event.target.value);
|
|
1539
|
+
setPage(1);
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
const handlePageSizeChange = (option: ISelectExtendedFieldDefaultOption) => {
|
|
1543
|
+
setPageSize(Number(option.value));
|
|
1544
|
+
setPage(1);
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
return (
|
|
1548
|
+
<MasterTable loading>
|
|
1549
|
+
<MasterTable.FilterPanel>
|
|
1550
|
+
<Row paddingBottom={false}>
|
|
1551
|
+
<Col size={6}>
|
|
1552
|
+
<TextField
|
|
1553
|
+
size={EComponentSize.MD}
|
|
1554
|
+
inputProps={{
|
|
1555
|
+
value: docNumberFilter,
|
|
1556
|
+
onChange: handleChangeDocNumber,
|
|
1557
|
+
placeholder: "Введите номер документа",
|
|
1558
|
+
}}
|
|
1559
|
+
label="Номер документа"
|
|
1560
|
+
/>
|
|
1561
|
+
</Col>
|
|
1562
|
+
<Col size={6}>
|
|
1563
|
+
<TextField
|
|
1564
|
+
size={EComponentSize.MD}
|
|
1565
|
+
inputProps={{
|
|
1566
|
+
value: recipientFilter,
|
|
1567
|
+
onChange: handleChangeRecipient,
|
|
1568
|
+
placeholder: "Введите получателя",
|
|
1569
|
+
}}
|
|
1570
|
+
label="Получатель"
|
|
1571
|
+
/>
|
|
1572
|
+
</Col>
|
|
1573
|
+
</Row>
|
|
1574
|
+
</MasterTable.FilterPanel>
|
|
1575
|
+
<MasterTable.TableBasic columns={columns} data={data} renderNoData={() => renderNoData(isFiltered)} />
|
|
1576
|
+
<MasterTable.PaginationPanel>
|
|
1577
|
+
<Pagination
|
|
1578
|
+
paginationNavigationProps={{
|
|
1579
|
+
currentPage,
|
|
1580
|
+
totalPages,
|
|
1581
|
+
boundaryCount: 1,
|
|
1582
|
+
siblingCount: 1,
|
|
1583
|
+
onCurrentPageChange: setPage,
|
|
1584
|
+
}}
|
|
1585
|
+
paginationSelectProps={{
|
|
1586
|
+
paginationLabel: "Показать на странице:",
|
|
1587
|
+
options: pageSizeOptions,
|
|
1588
|
+
value:
|
|
1589
|
+
pageSizeOptions.find((option) => option.value === String(pageSize)) || pageSizeOptions[0],
|
|
1590
|
+
onChange: handlePageSizeChange,
|
|
1591
|
+
targetProps: {
|
|
1592
|
+
fieldLabel: "",
|
|
1593
|
+
},
|
|
1594
|
+
}}
|
|
1595
|
+
/>
|
|
1596
|
+
</MasterTable.PaginationPanel>
|
|
1597
|
+
</MasterTable>
|
|
1598
|
+
);
|
|
1599
|
+
};
|
|
1600
|
+
```
|
|
1601
|
+
|
|
1602
|
+
### TableWithPaginationExtended
|
|
1603
|
+
|
|
1604
|
+
`stories/TableBasic/examples/TableWithPaginationExtended.tsx`
|
|
1605
|
+
|
|
1606
|
+
```tsx
|
|
1607
|
+
import React, { useState } from "react";
|
|
1608
|
+
import {
|
|
1609
|
+
Amount,
|
|
1610
|
+
EFontType,
|
|
1611
|
+
EHorizontalAlign,
|
|
1612
|
+
EPaginationNavigationIconDirection,
|
|
1613
|
+
ETextSize,
|
|
1614
|
+
Gap,
|
|
1615
|
+
MasterTable,
|
|
1616
|
+
PaginationExtended,
|
|
1617
|
+
PaginationNavigationButton,
|
|
1618
|
+
PaginationNavigationExtended,
|
|
1619
|
+
PaginationNavigationExtendedItem,
|
|
1620
|
+
PaginationPageButton,
|
|
1621
|
+
PaginationSelect,
|
|
1622
|
+
Text,
|
|
1623
|
+
ISelectExtendedFieldDefaultOption,
|
|
1624
|
+
ITableBasicColumn,
|
|
1625
|
+
ITableBasicRow,
|
|
1626
|
+
} from "@sberbusiness/triplex-next";
|
|
1627
|
+
|
|
1628
|
+
const renderCounterpartyDetails = (purpose: string, account: string, tax: string) => (
|
|
1629
|
+
<>
|
|
1630
|
+
<div>
|
|
1631
|
+
{purpose}
|
|
1632
|
+
<br />
|
|
1633
|
+
{account}
|
|
1634
|
+
</div>
|
|
1635
|
+
<Gap size={4} />
|
|
1636
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
1637
|
+
{tax}
|
|
1638
|
+
</Text>
|
|
1639
|
+
</>
|
|
1640
|
+
);
|
|
1641
|
+
|
|
1642
|
+
// Колонки и данные собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
1643
|
+
const columns: ITableBasicColumn[] = [
|
|
1644
|
+
{
|
|
1645
|
+
fieldKey: "number",
|
|
1646
|
+
label: "Номер",
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
fieldKey: "value",
|
|
1650
|
+
label: "Получатель",
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
fieldKey: "sum",
|
|
1654
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
1655
|
+
label: "Сумма",
|
|
1656
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
fieldKey: "status",
|
|
1660
|
+
label: "Статус",
|
|
1661
|
+
},
|
|
1662
|
+
];
|
|
1663
|
+
|
|
1664
|
+
const data: ITableBasicRow[] = Array.from({ length: 300 }, (_, index) => ({
|
|
1665
|
+
rowData: {
|
|
1666
|
+
number: 1000 + index,
|
|
1667
|
+
status: "Исполнено",
|
|
1668
|
+
sum: "1220000000",
|
|
1669
|
+
value: renderCounterpartyDetails(
|
|
1670
|
+
"Платежное поручение ООО Ромашка",
|
|
1671
|
+
"40702 810 2 0527 5000000",
|
|
1672
|
+
"В том числе НДС 20%",
|
|
1673
|
+
),
|
|
1674
|
+
},
|
|
1675
|
+
rowKey: `table-basic-row-${index}`,
|
|
1676
|
+
}));
|
|
1677
|
+
|
|
1678
|
+
const options: ISelectExtendedFieldDefaultOption[] = [
|
|
1679
|
+
{ id: "0", value: "10", label: "10" },
|
|
1680
|
+
{ id: "1", value: "20", label: "20" },
|
|
1681
|
+
{ id: "2", value: "50", label: "50" },
|
|
1682
|
+
{ id: "3", value: "100", label: "100" },
|
|
1683
|
+
];
|
|
1684
|
+
|
|
1685
|
+
export const TableWithPaginationExtended = () => {
|
|
1686
|
+
const [page, setPage] = useState(1);
|
|
1687
|
+
const [pageSize, setPageSize] = useState(10);
|
|
1688
|
+
const totalItems = data.length;
|
|
1689
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
|
|
1690
|
+
|
|
1691
|
+
const handlePageSizeChange = (option: ISelectExtendedFieldDefaultOption) => {
|
|
1692
|
+
setPage(1);
|
|
1693
|
+
setPageSize(Number(option.value));
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
const selectedOption = options.find((option) => option.value === String(pageSize));
|
|
1697
|
+
|
|
1698
|
+
const getPaginatedData = () => {
|
|
1699
|
+
return data.slice((page - 1) * pageSize, page * pageSize);
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
return (
|
|
1703
|
+
<MasterTable>
|
|
1704
|
+
<MasterTable.TableBasic
|
|
1705
|
+
columns={columns}
|
|
1706
|
+
data={getPaginatedData()}
|
|
1707
|
+
renderNoData={() => <div>Нет данных</div>}
|
|
1708
|
+
/>
|
|
1709
|
+
<MasterTable.PaginationPanel>
|
|
1710
|
+
<PaginationExtended>
|
|
1711
|
+
{totalPages > 1 && (
|
|
1712
|
+
<PaginationNavigationExtended>
|
|
1713
|
+
<PaginationNavigationExtendedItem>
|
|
1714
|
+
<PaginationNavigationButton
|
|
1715
|
+
direction={EPaginationNavigationIconDirection.BACK}
|
|
1716
|
+
onClick={() => setPage((prevPage) => Math.max(1, prevPage - 1))}
|
|
1717
|
+
disabled={page === 1}
|
|
1718
|
+
/>
|
|
1719
|
+
</PaginationNavigationExtendedItem>
|
|
1720
|
+
|
|
1721
|
+
<PaginationNavigationExtendedItem key="current-page">
|
|
1722
|
+
<PaginationPageButton isCurrent onClick={() => {}}>
|
|
1723
|
+
{page}
|
|
1724
|
+
</PaginationPageButton>
|
|
1725
|
+
</PaginationNavigationExtendedItem>
|
|
1726
|
+
|
|
1727
|
+
<PaginationNavigationExtendedItem>
|
|
1728
|
+
<PaginationNavigationButton
|
|
1729
|
+
direction={EPaginationNavigationIconDirection.NEXT}
|
|
1730
|
+
onClick={() => setPage((prevPage) => Math.min(totalPages, prevPage + 1))}
|
|
1731
|
+
disabled={page === totalPages}
|
|
1732
|
+
/>
|
|
1733
|
+
</PaginationNavigationExtendedItem>
|
|
1734
|
+
</PaginationNavigationExtended>
|
|
1735
|
+
)}
|
|
1736
|
+
<PaginationSelect
|
|
1737
|
+
paginationLabel="Показать на странице:"
|
|
1738
|
+
value={selectedOption || options[0]}
|
|
1739
|
+
options={options}
|
|
1740
|
+
onChange={handlePageSizeChange}
|
|
1741
|
+
targetProps={{
|
|
1742
|
+
fieldLabel: "",
|
|
1743
|
+
}}
|
|
1744
|
+
/>
|
|
1745
|
+
</PaginationExtended>
|
|
1746
|
+
</MasterTable.PaginationPanel>
|
|
1747
|
+
</MasterTable>
|
|
1748
|
+
);
|
|
1749
|
+
};
|
|
1750
|
+
```
|
|
1751
|
+
|
|
1752
|
+
### Example: production
|
|
1753
|
+
|
|
1754
|
+
`stories/TableBasic/examples/ExampleProduction.tsx`
|
|
1755
|
+
|
|
1756
|
+
```tsx
|
|
1757
|
+
import React, { useState } from "react";
|
|
1758
|
+
import { EmptytableSysIcon96, NotfoundSysIcon96 } from "@sberbusiness/icons-next";
|
|
1759
|
+
import {
|
|
1760
|
+
Amount,
|
|
1761
|
+
Button,
|
|
1762
|
+
Checkbox,
|
|
1763
|
+
CheckboxYGroup,
|
|
1764
|
+
Chip,
|
|
1765
|
+
ChipGroup,
|
|
1766
|
+
ChipMultiselect,
|
|
1767
|
+
Col,
|
|
1768
|
+
DropdownMobileBody,
|
|
1769
|
+
DropdownMobileClose,
|
|
1770
|
+
DropdownMobileFooter,
|
|
1771
|
+
DropdownMobileHeader,
|
|
1772
|
+
DropdownMobileInput,
|
|
1773
|
+
EButtonTheme,
|
|
1774
|
+
EComponentSize,
|
|
1775
|
+
EDropdownAlignment,
|
|
1776
|
+
EFontType,
|
|
1777
|
+
EFontWeightTitle,
|
|
1778
|
+
EHorizontalAlign,
|
|
1779
|
+
EMarkerStatus,
|
|
1780
|
+
ETextSize,
|
|
1781
|
+
ETitleSize,
|
|
1782
|
+
FormField,
|
|
1783
|
+
FormFieldClear,
|
|
1784
|
+
FormFieldInput,
|
|
1785
|
+
FormFieldLabel,
|
|
1786
|
+
FormFieldPostfix,
|
|
1787
|
+
Gap,
|
|
1788
|
+
Link,
|
|
1789
|
+
MarkerStatus,
|
|
1790
|
+
MasterTable,
|
|
1791
|
+
MultiselectField,
|
|
1792
|
+
Row,
|
|
1793
|
+
SelectField,
|
|
1794
|
+
Tag,
|
|
1795
|
+
TagGroup,
|
|
1796
|
+
Text,
|
|
1797
|
+
TextField,
|
|
1798
|
+
Title,
|
|
1799
|
+
ISelectFieldOption,
|
|
1800
|
+
ITableBasicColumn,
|
|
1801
|
+
ITableBasicRow,
|
|
1802
|
+
} from "@sberbusiness/triplex-next";
|
|
1803
|
+
|
|
1804
|
+
/** Ключи полей таблицы. */
|
|
1805
|
+
enum ETableField {
|
|
1806
|
+
Number = "Number",
|
|
1807
|
+
Recipient = "Recipient",
|
|
1808
|
+
Sum = "Sum",
|
|
1809
|
+
Status = "Status",
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
interface IExampleExtraFilters {
|
|
1813
|
+
recipientOption: ISelectFieldOption | undefined;
|
|
1814
|
+
docNumber: string;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
const renderCounterpartyDetails = (purpose: string, account: string, tax: string) => (
|
|
1818
|
+
<>
|
|
1819
|
+
<div>
|
|
1820
|
+
{purpose}
|
|
1821
|
+
<br />
|
|
1822
|
+
{account}
|
|
1823
|
+
</div>
|
|
1824
|
+
<Gap size={4} />
|
|
1825
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
1826
|
+
{tax}
|
|
1827
|
+
</Text>
|
|
1828
|
+
</>
|
|
1829
|
+
);
|
|
1830
|
+
|
|
1831
|
+
const renderNoData = (isFiltered: boolean) => (
|
|
1832
|
+
<>
|
|
1833
|
+
{isFiltered ? <NotfoundSysIcon96 /> : <EmptytableSysIcon96 />}
|
|
1834
|
+
<Gap size={8} />
|
|
1835
|
+
<Title size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
|
|
1836
|
+
Текст заголовка
|
|
1837
|
+
</Title>
|
|
1838
|
+
<Gap size={12} />
|
|
1839
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
1840
|
+
Нет данных, но можно предложить какие-то действия для заполнения таблицы
|
|
1841
|
+
</Text>
|
|
1842
|
+
<Gap size={24} />
|
|
1843
|
+
<div>
|
|
1844
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD}>
|
|
1845
|
+
Button text
|
|
1846
|
+
</Button>
|
|
1847
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
1848
|
+
Button text
|
|
1849
|
+
</Button>
|
|
1850
|
+
</div>
|
|
1851
|
+
</>
|
|
1852
|
+
);
|
|
1853
|
+
|
|
1854
|
+
const segments: { id: string; label: string; status: string | null; withNotification?: boolean }[] = [
|
|
1855
|
+
{ id: "all", label: "Все", status: null },
|
|
1856
|
+
{ id: "drafts", label: "Черновики", status: "Создан" },
|
|
1857
|
+
{ id: "toSign", label: "На подпись и отправку", status: "Подписан", withNotification: true },
|
|
1858
|
+
{ id: "executed", label: "Исполненные", status: "Оплачен" },
|
|
1859
|
+
];
|
|
1860
|
+
|
|
1861
|
+
const statusFilterOptions = [
|
|
1862
|
+
{ id: "created", label: "Создан" },
|
|
1863
|
+
{ id: "signed", label: "Подписан" },
|
|
1864
|
+
{ id: "paid", label: "Оплачен" },
|
|
1865
|
+
];
|
|
1866
|
+
|
|
1867
|
+
const statusToMarker: Record<string, EMarkerStatus> = {
|
|
1868
|
+
Создан: EMarkerStatus.WAITING,
|
|
1869
|
+
Подписан: EMarkerStatus.WARNING,
|
|
1870
|
+
Оплачен: EMarkerStatus.SUCCESS,
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
const counterpartyOptions: ISelectFieldOption[] = [
|
|
1874
|
+
{ id: "0", value: "", label: "Не указан" },
|
|
1875
|
+
{ id: "1", value: "ООО Ромашка", label: "ООО Ромашка" },
|
|
1876
|
+
{ id: "2", value: "ИП Иванов Иван Иванович", label: "ИП Иванов Иван Иванович" },
|
|
1877
|
+
];
|
|
1878
|
+
|
|
1879
|
+
const documents = [
|
|
1880
|
+
{
|
|
1881
|
+
docNumber: "1350",
|
|
1882
|
+
recipient: { account: "40702 810 2 9045 8100009", name: "ИП Иванов Иван Иванович" },
|
|
1883
|
+
purpose: "Платёжное поручение ИП Иванов Иван Иванович за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1884
|
+
tax: "В том числе НДС 20%",
|
|
1885
|
+
sum: "9450.00",
|
|
1886
|
+
status: "Создан",
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
docNumber: "1351",
|
|
1890
|
+
recipient: { account: "40702 810 2 0527 5520080", name: "ООО Ромашка" },
|
|
1891
|
+
purpose: "Платёжное поручение ООО Ромашка за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1892
|
+
tax: "В том числе НДС 20%",
|
|
1893
|
+
sum: "18914.00",
|
|
1894
|
+
status: "Создан",
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
docNumber: "1352",
|
|
1898
|
+
recipient: { account: "40702 810 2 2581 6403700", name: "ИП Иванов Иван Иванович" },
|
|
1899
|
+
purpose: "Платёжное поручение ИП Иванов Иван Иванович за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1900
|
+
tax: "В том числе НДС 20%",
|
|
1901
|
+
sum: "28392.00",
|
|
1902
|
+
status: "Подписан",
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
docNumber: "1353",
|
|
1906
|
+
recipient: { account: "40702 810 2 8302 7306400", name: "ООО Ромашка" },
|
|
1907
|
+
purpose: "Платёжное поручение ООО Ромашка за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1908
|
+
tax: "В том числе НДС 20%",
|
|
1909
|
+
sum: "37884.00",
|
|
1910
|
+
status: "Подписан",
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
docNumber: "1354",
|
|
1914
|
+
recipient: { account: "40702 810 2 9947 8250050", name: "ИП Иванов Иван Иванович" },
|
|
1915
|
+
purpose: "Платёжное поручение ИП Иванов Иван Иванович за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1916
|
+
tax: "В том числе НДС 20%",
|
|
1917
|
+
sum: "47390.00",
|
|
1918
|
+
status: "Оплачен",
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
docNumber: "1355",
|
|
1922
|
+
recipient: { account: "40702 810 2 0290 9100406", name: "ООО Ромашка" },
|
|
1923
|
+
purpose: "Платёжное поручение ООО Ромашка за оказание услуг по акту №67834259-ТНГК2356-345",
|
|
1924
|
+
tax: "В том числе НДС 20%",
|
|
1925
|
+
sum: "56910.00",
|
|
1926
|
+
status: "Оплачен",
|
|
1927
|
+
},
|
|
1928
|
+
];
|
|
1929
|
+
|
|
1930
|
+
// Колонки собираются вне рендера, чтобы таблица не обновляла контекст MasterTable на каждый рендер.
|
|
1931
|
+
const columns: ITableBasicColumn[] = [
|
|
1932
|
+
{ fieldKey: ETableField.Number, label: "Номер", title: "Номер", width: 100 },
|
|
1933
|
+
{ fieldKey: ETableField.Recipient, label: "Получатель", title: "Получатель", width: 320 },
|
|
1934
|
+
{
|
|
1935
|
+
fieldKey: ETableField.Sum,
|
|
1936
|
+
label: "Сумма",
|
|
1937
|
+
title: "Сумма",
|
|
1938
|
+
horizontalAlign: EHorizontalAlign.RIGHT,
|
|
1939
|
+
renderCell: (fieldValue) => fieldValue && <Amount value={fieldValue} currency="RUB" />,
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
fieldKey: ETableField.Status,
|
|
1943
|
+
label: "Статус",
|
|
1944
|
+
title: "Статус",
|
|
1945
|
+
width: 140,
|
|
1946
|
+
renderCell: (status: string) => (
|
|
1947
|
+
<MarkerStatus size={EComponentSize.LG} status={statusToMarker[status] ?? EMarkerStatus.WAITING}>
|
|
1948
|
+
{status}
|
|
1949
|
+
</MarkerStatus>
|
|
1950
|
+
),
|
|
1951
|
+
},
|
|
1952
|
+
];
|
|
1953
|
+
|
|
1954
|
+
const defaultExtraFilters: IExampleExtraFilters = { recipientOption: undefined, docNumber: "" };
|
|
1955
|
+
|
|
1956
|
+
export const ExampleProduction = () => {
|
|
1957
|
+
const [activeSegment, setActiveSegment] = useState("all");
|
|
1958
|
+
const [selectedStatusIds, setSelectedStatusIds] = useState<string[]>([]);
|
|
1959
|
+
const [statusFilter, setStatusFilter] = useState("");
|
|
1960
|
+
const [recipientQuery, setRecipientQuery] = useState("");
|
|
1961
|
+
const [showExtraFilters, setShowExtraFilters] = useState(false);
|
|
1962
|
+
const [appliedExtraFilters, setAppliedExtraFilters] = useState<IExampleExtraFilters>(defaultExtraFilters);
|
|
1963
|
+
const [tempExtraFilters, setTempExtraFilters] = useState<IExampleExtraFilters>(defaultExtraFilters);
|
|
1964
|
+
|
|
1965
|
+
const selectedStatusLabels = statusFilterOptions
|
|
1966
|
+
.filter((option) => selectedStatusIds.includes(option.id))
|
|
1967
|
+
.map((option) => option.label);
|
|
1968
|
+
|
|
1969
|
+
const segmentStatus = segments.find((segment) => segment.id === activeSegment)?.status ?? null;
|
|
1970
|
+
|
|
1971
|
+
const isExtraFilterApplied =
|
|
1972
|
+
Boolean(appliedExtraFilters.recipientOption?.value) || Boolean(appliedExtraFilters.docNumber);
|
|
1973
|
+
|
|
1974
|
+
const isAnyFilterApplied = selectedStatusLabels.length > 0 || Boolean(recipientQuery) || isExtraFilterApplied;
|
|
1975
|
+
|
|
1976
|
+
const filteredData = documents.filter((doc) => {
|
|
1977
|
+
if (segmentStatus && doc.status !== segmentStatus) {
|
|
1978
|
+
return false;
|
|
1979
|
+
}
|
|
1980
|
+
if (selectedStatusLabels.length > 0 && !selectedStatusLabels.includes(doc.status)) {
|
|
1981
|
+
return false;
|
|
1982
|
+
}
|
|
1983
|
+
if (recipientQuery && !doc.recipient.name.toLowerCase().includes(recipientQuery.toLowerCase())) {
|
|
1984
|
+
return false;
|
|
1985
|
+
}
|
|
1986
|
+
if (
|
|
1987
|
+
appliedExtraFilters.recipientOption?.value &&
|
|
1988
|
+
doc.recipient.name !== appliedExtraFilters.recipientOption.value
|
|
1989
|
+
) {
|
|
1990
|
+
return false;
|
|
1991
|
+
}
|
|
1992
|
+
if (appliedExtraFilters.docNumber && !doc.docNumber.includes(appliedExtraFilters.docNumber)) {
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
return true;
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
const data: ITableBasicRow[] = filteredData.map((doc) => ({
|
|
1999
|
+
rowKey: doc.docNumber,
|
|
2000
|
+
rowData: {
|
|
2001
|
+
[ETableField.Number]: doc.docNumber,
|
|
2002
|
+
[ETableField.Recipient]: renderCounterpartyDetails(doc.purpose, doc.recipient.account, doc.tax),
|
|
2003
|
+
[ETableField.Sum]: doc.sum,
|
|
2004
|
+
[ETableField.Status]: doc.status,
|
|
2005
|
+
},
|
|
2006
|
+
}));
|
|
2007
|
+
|
|
2008
|
+
const visibleStatusOptions = statusFilterOptions.filter((option) =>
|
|
2009
|
+
option.label.toLowerCase().includes(statusFilter.trim().toLowerCase()),
|
|
2010
|
+
);
|
|
2011
|
+
|
|
2012
|
+
const toggleStatus = (optionId: string, checked: boolean) => {
|
|
2013
|
+
setSelectedStatusIds((prev) => (checked ? [...prev, optionId] : prev.filter((id) => id !== optionId)));
|
|
2014
|
+
};
|
|
2015
|
+
|
|
2016
|
+
const unselectAllStatuses = () => {
|
|
2017
|
+
setSelectedStatusIds([]);
|
|
2018
|
+
setStatusFilter("");
|
|
2019
|
+
};
|
|
2020
|
+
|
|
2021
|
+
const handleStatusFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
2022
|
+
setStatusFilter(event.target.value);
|
|
2023
|
+
};
|
|
2024
|
+
|
|
2025
|
+
const handleClickToggleExtraFilters = () => {
|
|
2026
|
+
setTempExtraFilters(appliedExtraFilters);
|
|
2027
|
+
setShowExtraFilters((prev) => !prev);
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
const handleClickResetTempFilters = () => {
|
|
2031
|
+
setTempExtraFilters(defaultExtraFilters);
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
const handleClickApplyTempFilters = () => {
|
|
2035
|
+
setAppliedExtraFilters(tempExtraFilters);
|
|
2036
|
+
setShowExtraFilters(false);
|
|
2037
|
+
};
|
|
2038
|
+
|
|
2039
|
+
const handleRemoveTag = (newFilters: IExampleExtraFilters) => () => {
|
|
2040
|
+
setTempExtraFilters(newFilters);
|
|
2041
|
+
setAppliedExtraFilters(newFilters);
|
|
2042
|
+
};
|
|
2043
|
+
|
|
2044
|
+
const handleClickResetAllFilters = () => {
|
|
2045
|
+
unselectAllStatuses();
|
|
2046
|
+
setRecipientQuery("");
|
|
2047
|
+
setTempExtraFilters(defaultExtraFilters);
|
|
2048
|
+
setAppliedExtraFilters(defaultExtraFilters);
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
const renderStatusCheckboxList = () => (
|
|
2052
|
+
<CheckboxYGroup aria-label="Статус">
|
|
2053
|
+
{visibleStatusOptions.map((option) => (
|
|
2054
|
+
<Checkbox
|
|
2055
|
+
key={option.id}
|
|
2056
|
+
id={option.id}
|
|
2057
|
+
size={EComponentSize.MD}
|
|
2058
|
+
checked={selectedStatusIds.includes(option.id)}
|
|
2059
|
+
onChange={(event) => toggleStatus(option.id, event.target.checked)}
|
|
2060
|
+
>
|
|
2061
|
+
{option.label}
|
|
2062
|
+
</Checkbox>
|
|
2063
|
+
))}
|
|
2064
|
+
</CheckboxYGroup>
|
|
2065
|
+
);
|
|
2066
|
+
|
|
2067
|
+
const renderStatusDropdownContent = () => {
|
|
2068
|
+
const hasResults = !statusFilter.trim() || visibleStatusOptions.length > 0;
|
|
2069
|
+
|
|
2070
|
+
return hasResults ? (
|
|
2071
|
+
renderStatusCheckboxList()
|
|
2072
|
+
) : (
|
|
2073
|
+
<div style={{ padding: "8px 16px" }}>
|
|
2074
|
+
<Text size={ETextSize.B3}>Ничего не найдено</Text>
|
|
2075
|
+
</div>
|
|
2076
|
+
);
|
|
2077
|
+
};
|
|
2078
|
+
|
|
2079
|
+
const renderStatusMultiselect = () => (
|
|
2080
|
+
<ChipMultiselect
|
|
2081
|
+
size={EComponentSize.MD}
|
|
2082
|
+
clearSelected={unselectAllStatuses}
|
|
2083
|
+
selected={selectedStatusIds.length > 0}
|
|
2084
|
+
label="Статус"
|
|
2085
|
+
displayedValue={`Статус: ${selectedStatusLabels.join(", ")}`}
|
|
2086
|
+
>
|
|
2087
|
+
{({ opened, setOpened, targetRef, dropdownRef }) => (
|
|
2088
|
+
<MultiselectField.Dropdown
|
|
2089
|
+
opened={opened}
|
|
2090
|
+
setOpened={setOpened}
|
|
2091
|
+
targetRef={targetRef}
|
|
2092
|
+
ref={dropdownRef}
|
|
2093
|
+
alignment={EDropdownAlignment.LEFT}
|
|
2094
|
+
focusTrapProps={{ focusTrapOptions: { initialFocus: false } }}
|
|
2095
|
+
mobileViewProps={{
|
|
2096
|
+
children: (
|
|
2097
|
+
<>
|
|
2098
|
+
<DropdownMobileHeader
|
|
2099
|
+
controlButtons={<DropdownMobileClose onClick={() => setOpened(false)} />}
|
|
2100
|
+
>
|
|
2101
|
+
<DropdownMobileInput
|
|
2102
|
+
autoFocus
|
|
2103
|
+
placeholder="Поиск по статусу"
|
|
2104
|
+
value={statusFilter}
|
|
2105
|
+
onChange={handleStatusFilterChange}
|
|
2106
|
+
/>
|
|
2107
|
+
</DropdownMobileHeader>
|
|
2108
|
+
<DropdownMobileBody>{renderStatusDropdownContent()}</DropdownMobileBody>
|
|
2109
|
+
<DropdownMobileFooter>
|
|
2110
|
+
<Button
|
|
2111
|
+
theme={EButtonTheme.GENERAL}
|
|
2112
|
+
size={EComponentSize.SM}
|
|
2113
|
+
onClick={() => setOpened(false)}
|
|
2114
|
+
>
|
|
2115
|
+
Применить
|
|
2116
|
+
</Button>
|
|
2117
|
+
<Button
|
|
2118
|
+
theme={EButtonTheme.LINK}
|
|
2119
|
+
size={EComponentSize.SM}
|
|
2120
|
+
onClick={unselectAllStatuses}
|
|
2121
|
+
>
|
|
2122
|
+
Сбросить
|
|
2123
|
+
</Button>
|
|
2124
|
+
</DropdownMobileFooter>
|
|
2125
|
+
</>
|
|
2126
|
+
),
|
|
2127
|
+
}}
|
|
2128
|
+
>
|
|
2129
|
+
<MultiselectField.Dropdown.Header>
|
|
2130
|
+
<FormField size={EComponentSize.SM}>
|
|
2131
|
+
<FormFieldLabel>Поиск по статусу</FormFieldLabel>
|
|
2132
|
+
<FormFieldInput value={statusFilter} onChange={handleStatusFilterChange} />
|
|
2133
|
+
<FormFieldPostfix>
|
|
2134
|
+
<FormFieldClear onClick={() => setStatusFilter("")} />
|
|
2135
|
+
</FormFieldPostfix>
|
|
2136
|
+
</FormField>
|
|
2137
|
+
</MultiselectField.Dropdown.Header>
|
|
2138
|
+
<MultiselectField.Dropdown.Content>
|
|
2139
|
+
{renderStatusDropdownContent()}
|
|
2140
|
+
</MultiselectField.Dropdown.Content>
|
|
2141
|
+
<MultiselectField.Dropdown.Footer>
|
|
2142
|
+
<Button
|
|
2143
|
+
theme={EButtonTheme.SECONDARY}
|
|
2144
|
+
size={EComponentSize.SM}
|
|
2145
|
+
onClick={() => setOpened(false)}
|
|
2146
|
+
>
|
|
2147
|
+
Применить
|
|
2148
|
+
</Button>
|
|
2149
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.SM} onClick={unselectAllStatuses}>
|
|
2150
|
+
Сбросить
|
|
2151
|
+
</Button>
|
|
2152
|
+
</MultiselectField.Dropdown.Footer>
|
|
2153
|
+
</MultiselectField.Dropdown>
|
|
2154
|
+
)}
|
|
2155
|
+
</ChipMultiselect>
|
|
2156
|
+
);
|
|
2157
|
+
|
|
2158
|
+
const renderExtraFilters = () => (
|
|
2159
|
+
<>
|
|
2160
|
+
<Gap size={12} />
|
|
2161
|
+
<Row paddingBottom={false}>
|
|
2162
|
+
<Col size={6}>
|
|
2163
|
+
<SelectField
|
|
2164
|
+
size={EComponentSize.MD}
|
|
2165
|
+
value={tempExtraFilters.recipientOption}
|
|
2166
|
+
options={counterpartyOptions}
|
|
2167
|
+
onChange={(option) =>
|
|
2168
|
+
setTempExtraFilters((prev) => ({
|
|
2169
|
+
...prev,
|
|
2170
|
+
recipientOption: option.value ? option : undefined,
|
|
2171
|
+
}))
|
|
2172
|
+
}
|
|
2173
|
+
placeholder="Выберите получателя из списка"
|
|
2174
|
+
targetProps={{ fieldLabel: "Получатель" }}
|
|
2175
|
+
/>
|
|
2176
|
+
</Col>
|
|
2177
|
+
<Col size={6}>
|
|
2178
|
+
<TextField
|
|
2179
|
+
size={EComponentSize.MD}
|
|
2180
|
+
inputProps={{
|
|
2181
|
+
value: tempExtraFilters.docNumber,
|
|
2182
|
+
onChange: (event) =>
|
|
2183
|
+
setTempExtraFilters((prev) => ({ ...prev, docNumber: event.target.value })),
|
|
2184
|
+
placeholder: "Введите номер документа",
|
|
2185
|
+
}}
|
|
2186
|
+
label="Номер документа"
|
|
2187
|
+
/>
|
|
2188
|
+
</Col>
|
|
2189
|
+
</Row>
|
|
2190
|
+
<Gap size={12} />
|
|
2191
|
+
<div style={{ display: "flex", justifyContent: "end" }}>
|
|
2192
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={handleClickResetTempFilters}>
|
|
2193
|
+
Сбросить
|
|
2194
|
+
</Button>
|
|
2195
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD} onClick={handleClickApplyTempFilters}>
|
|
2196
|
+
Применить
|
|
2197
|
+
</Button>
|
|
2198
|
+
</div>
|
|
2199
|
+
</>
|
|
2200
|
+
);
|
|
2201
|
+
|
|
2202
|
+
const renderExtraFilterTags = () => {
|
|
2203
|
+
const tags: React.JSX.Element[] = [];
|
|
2204
|
+
|
|
2205
|
+
if (appliedExtraFilters.recipientOption) {
|
|
2206
|
+
tags.push(
|
|
2207
|
+
<Tag
|
|
2208
|
+
key="tag-filter-recipient"
|
|
2209
|
+
id="tag-filter-recipient"
|
|
2210
|
+
size={EComponentSize.MD}
|
|
2211
|
+
onRemove={handleRemoveTag({ ...appliedExtraFilters, recipientOption: undefined })}
|
|
2212
|
+
>
|
|
2213
|
+
{`Получатель: ${appliedExtraFilters.recipientOption.value}`}
|
|
2214
|
+
</Tag>,
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
if (appliedExtraFilters.docNumber) {
|
|
2219
|
+
tags.push(
|
|
2220
|
+
<Tag
|
|
2221
|
+
key="tag-filter-doc-number"
|
|
2222
|
+
id="tag-filter-doc-number"
|
|
2223
|
+
size={EComponentSize.MD}
|
|
2224
|
+
onRemove={handleRemoveTag({ ...appliedExtraFilters, docNumber: "" })}
|
|
2225
|
+
>
|
|
2226
|
+
{`Номер документа: ${appliedExtraFilters.docNumber}`}
|
|
2227
|
+
</Tag>,
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
return (
|
|
2232
|
+
<>
|
|
2233
|
+
<Gap size={12} />
|
|
2234
|
+
<TagGroup size={EComponentSize.MD}>{tags}</TagGroup>
|
|
2235
|
+
</>
|
|
2236
|
+
);
|
|
2237
|
+
};
|
|
2238
|
+
|
|
2239
|
+
const renderAdditionalFilterArea = () => {
|
|
2240
|
+
if (showExtraFilters) {
|
|
2241
|
+
return renderExtraFilters();
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
return isExtraFilterApplied ? renderExtraFilterTags() : null;
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
return (
|
|
2248
|
+
<MasterTable>
|
|
2249
|
+
<MasterTable.ChipPanel>
|
|
2250
|
+
<ChipGroup size={EComponentSize.SM} oneLine>
|
|
2251
|
+
{segments.map((segment) => (
|
|
2252
|
+
<Chip
|
|
2253
|
+
key={segment.id}
|
|
2254
|
+
size={EComponentSize.SM}
|
|
2255
|
+
selected={activeSegment === segment.id}
|
|
2256
|
+
showNotificationIcon={segment.withNotification}
|
|
2257
|
+
onClick={() => setActiveSegment(segment.id)}
|
|
2258
|
+
>
|
|
2259
|
+
{segment.label}
|
|
2260
|
+
</Chip>
|
|
2261
|
+
))}
|
|
2262
|
+
</ChipGroup>
|
|
2263
|
+
<MasterTable.ChipPanel.Links>
|
|
2264
|
+
{isExtraFilterApplied && <Link onClick={handleClickResetAllFilters}>Сбросить всё</Link>}
|
|
2265
|
+
<Link onClick={handleClickToggleExtraFilters}>
|
|
2266
|
+
{showExtraFilters ? "Скрыть фильтры" : isExtraFilterApplied ? "Изменить фильтры" : "Фильтры"}
|
|
2267
|
+
</Link>
|
|
2268
|
+
</MasterTable.ChipPanel.Links>
|
|
2269
|
+
</MasterTable.ChipPanel>
|
|
2270
|
+
<MasterTable.FilterPanel>
|
|
2271
|
+
<div style={{ display: "flex", alignItems: "flex-start", gap: 12, flexWrap: "wrap" }}>
|
|
2272
|
+
{renderStatusMultiselect()}
|
|
2273
|
+
<TextField
|
|
2274
|
+
size={EComponentSize.MD}
|
|
2275
|
+
inputProps={{
|
|
2276
|
+
value: recipientQuery,
|
|
2277
|
+
onChange: (event) => setRecipientQuery(event.target.value),
|
|
2278
|
+
placeholder: "Введите получателя",
|
|
2279
|
+
}}
|
|
2280
|
+
label="Получатель"
|
|
2281
|
+
/>
|
|
2282
|
+
</div>
|
|
2283
|
+
{renderAdditionalFilterArea()}
|
|
2284
|
+
</MasterTable.FilterPanel>
|
|
2285
|
+
<MasterTable.TableBasic
|
|
2286
|
+
columns={columns}
|
|
2287
|
+
data={data}
|
|
2288
|
+
renderNoData={() => renderNoData(isAnyFilterApplied)}
|
|
2289
|
+
/>
|
|
2290
|
+
</MasterTable>
|
|
2291
|
+
);
|
|
2292
|
+
};
|
|
2293
|
+
```
|
|
2294
|
+
|