@sberbusiness/triplex-next-mcp-server 0.24.0-beta2 → 1.40.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/README.md +10 -0
- package/bundle/components/AlertContext.md +268 -0
- package/bundle/components/BodyPage.md +186 -0
- package/bundle/components/Button.md +93 -85
- package/bundle/components/ButtonDropdown.md +36 -70
- package/bundle/components/ButtonDropdownExtended.md +422 -0
- package/bundle/components/ButtonIcon.md +41 -48
- package/bundle/components/Caption.md +291 -0
- package/bundle/components/Checkbox.md +43 -122
- package/bundle/components/CheckboxXGroup.md +109 -46
- package/bundle/components/CheckboxYGroup.md +77 -49
- package/bundle/components/CodeText.md +197 -0
- package/bundle/components/Col.md +311 -162
- package/bundle/components/CollapsibleTree.md +584 -0
- package/bundle/components/CollapsibleTreeExtended.md +422 -0
- package/bundle/components/Dropdown.md +748 -346
- package/bundle/components/EmptyView.md +14 -15
- package/bundle/components/FooterPage.md +203 -0
- package/bundle/components/Gap.md +115 -57
- package/bundle/components/HeaderPage.md +226 -0
- package/bundle/components/HelpBox.md +45 -84
- package/bundle/components/ImageGallery.md +423 -0
- package/bundle/components/ImageGalleryExtended.md +606 -0
- package/bundle/components/LightBox.md +2252 -233
- package/bundle/components/List.md +216 -183
- package/bundle/components/ListActionItem.md +157 -0
- package/bundle/components/ListItem.md +276 -0
- package/bundle/components/ListItemControlsButton.md +103 -0
- package/bundle/components/ListItemControlsButtonDropdown.md +107 -0
- package/bundle/components/ListItemSelectable.md +99 -0
- package/bundle/components/ListItemTable.md +390 -0
- package/bundle/components/ListMaster.md +876 -156
- package/bundle/components/ListMasterFooter.md +80 -0
- package/bundle/components/ListMasterHeader.md +86 -0
- package/bundle/components/ListSortable.md +153 -0
- package/bundle/components/ListSortableItem.md +175 -0
- package/bundle/components/ListTableItem.md +391 -0
- package/bundle/components/ModalWindow.md +427 -237
- package/bundle/components/MonthYearField.md +145 -109
- package/bundle/components/Notification.md +625 -155
- package/bundle/components/Overlay.md +271 -170
- package/bundle/components/Page.md +284 -186
- package/bundle/components/Pagination.md +206 -229
- package/bundle/components/Portal.md +131 -67
- package/bundle/components/Row.md +171 -69
- package/bundle/components/Text.md +320 -0
- package/bundle/components/TextField.md +364 -97
- package/bundle/components/Title.md +296 -0
- package/bundle/guides/codestyle.md +38 -11
- package/bundle/guides/commits.md +119 -26
- package/bundle/guides/context.md +34 -30
- package/bundle/guides/stories.md +110 -40
- package/bundle/guides/template.md +3 -7
- package/bundle/guides/tests.md +48 -2
- package/bundle/manifest.json +275 -30
- package/bundle/release-notes/1.26.0.mdx +33 -2
- package/bundle/release-notes/1.27.0.mdx +43 -0
- package/bundle/release-notes/1.28.0.mdx +25 -0
- package/bundle/release-notes/1.29.0.mdx +47 -0
- package/bundle/release-notes/1.30.0.mdx +56 -0
- package/bundle/release-notes/1.31.0.mdx +52 -0
- package/bundle/release-notes/1.32.0.mdx +35 -0
- package/bundle/release-notes/1.33.0.mdx +61 -0
- package/bundle/release-notes/1.34.0.mdx +35 -0
- package/bundle/release-notes/1.35.0.mdx +21 -0
- package/bundle/release-notes/1.35.1.mdx +11 -0
- package/bundle/release-notes/1.36.0.mdx +21 -0
- package/bundle/release-notes/1.37.0.mdx +71 -0
- package/bundle/release-notes/1.38.0.mdx +36 -0
- package/bundle/release-notes/1.39.0.mdx +66 -0
- package/bundle/release-notes/1.40.0.mdx +27 -0
- package/bundle/release-notes/1.41.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: ListMaster
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [List, ChipGroup, Checkbox]
|
|
3
|
+
category: List
|
|
4
|
+
related: [ListMasterHeader, ListMasterFooter, List, ChipGroup]
|
|
7
5
|
tokens: []
|
|
8
|
-
stories:
|
|
6
|
+
stories: stories/ListMaster/ListMaster.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
10
8
|
---
|
|
11
9
|
|
|
@@ -13,192 +11,914 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
`ListMaster.Header` / `.Body` / `.Footer` / `.ChipGroup` / `.SelectionControls`.
|
|
14
|
+
Compound-компонент, оборачивающий список и фильтры в единый layout. Объединяет
|
|
15
|
+
sticky-шапку (selection-controls), тело со списком и `ChipGroup`-фильтрами,
|
|
16
|
+
sticky-футер с агрегатами и действиями. Сам по себе — простой `<div>`-контейнер;
|
|
17
|
+
смысл придают статические подкомпоненты.
|
|
21
18
|
|
|
22
|
-
Используй когда:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Файловая структура
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
src/components/ListMaster/
|
|
31
|
-
├── ListMaster.tsx # <div>, compound через Object.assign
|
|
32
|
-
├── index.ts # Barrel
|
|
33
|
-
├── components/
|
|
34
|
-
│ ├── ListMasterHeader.tsx # sticky + scroll-компенсация при mount/unmount
|
|
35
|
-
│ ├── ListMasterBody.tsx # прозрачный <div>-wrapper
|
|
36
|
-
│ ├── ListMasterFooter.tsx # sticky
|
|
37
|
-
│ ├── ListMasterFooterControls.tsx
|
|
38
|
-
│ ├── ListMasterFooterDescription.tsx
|
|
39
|
-
│ ├── ListMasterChipGroup.tsx # обёртка над <ChipGroup oneLine>
|
|
40
|
-
│ └── SelectionControls.tsx # блок пакетных действий
|
|
41
|
-
└── styles/
|
|
42
|
-
├── ListMasterHeader.module.less
|
|
43
|
-
├── ListMasterFooter.module.less
|
|
44
|
-
├── ListMasterChipGroup.module.less
|
|
45
|
-
└── SelectionControls.module.less
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Compound-структура
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
<ListMaster>
|
|
54
|
-
<ListMaster.Header sticky>
|
|
55
|
-
{/* Заголовок страницы, поиск, крупные фильтры */}
|
|
56
|
-
</ListMaster.Header>
|
|
57
|
-
|
|
58
|
-
<ListMaster.ChipGroup>
|
|
59
|
-
<Chip>Фильтр 1</Chip>
|
|
60
|
-
<Chip>Фильтр 2</Chip>
|
|
61
|
-
</ListMaster.ChipGroup>
|
|
62
|
-
|
|
63
|
-
<ListMaster.SelectionControls>
|
|
64
|
-
{/* Кнопки массовых действий: "Удалить", "Экспортировать" */}
|
|
65
|
-
</ListMaster.SelectionControls>
|
|
66
|
-
|
|
67
|
-
<ListMaster.Body>
|
|
68
|
-
<List>{/* ListItem'ы */}</List>
|
|
69
|
-
</ListMaster.Body>
|
|
70
|
-
|
|
71
|
-
<ListMaster.Footer sticky>
|
|
72
|
-
<ListMaster.FooterDescription>{/* "Показано 10 из 100" */}</ListMaster.FooterDescription>
|
|
73
|
-
<ListMaster.FooterControls>{/* Pagination */}</ListMaster.FooterControls>
|
|
74
|
-
</ListMaster.Footer>
|
|
75
|
-
</ListMaster>
|
|
76
|
-
```
|
|
19
|
+
Используй `ListMaster` когда: страница со списком, где нужны фильтры, выбор
|
|
20
|
+
элементов и действия по выбранному.
|
|
77
21
|
|
|
78
22
|
---
|
|
79
23
|
|
|
80
24
|
## Варианты и props
|
|
81
25
|
|
|
82
|
-
###
|
|
83
|
-
|
|
84
|
-
Без собственных props'ов. Просто `<div>` + `data-tx` + compound-подкомпоненты.
|
|
26
|
+
### Опциональные props (`ListMaster`)
|
|
85
27
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| Prop | Тип | Default | Описание |
|
|
28
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
89
29
|
|---|---|---|---|
|
|
90
|
-
| `
|
|
91
|
-
|
|
92
|
-
**Scroll-компенсация при mount/unmount** — см. ниже.
|
|
30
|
+
| `children` | `React.ReactNode` | — | Композиция из статических подкомпонентов |
|
|
31
|
+
| `...HTMLDivAttributes` | — | — | Все стандартные атрибуты `<div>` |
|
|
93
32
|
|
|
94
|
-
###
|
|
33
|
+
### Статические подкомпоненты (compound API)
|
|
95
34
|
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
| `
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Обёртка над `<ChipGroup oneLine>` (из компонента `ChipGroup`) с доп. стилем (horizontal padding). **`oneLine` зашит** — нельзя отключить через props.
|
|
107
|
-
|
|
108
|
-
### ListMaster.Body
|
|
109
|
-
|
|
110
|
-
Прозрачный forwardRef-wrapper, без стилей.
|
|
111
|
-
|
|
112
|
-
### ListMaster.SelectionControls
|
|
113
|
-
|
|
114
|
-
`<div>` с классом `.selectionControls`. Рендерится, когда нужно показать действия над выбранными элементами (например, по флагу контроллера).
|
|
35
|
+
| Имя | Назначение | Отдельный AI.md |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `ListMaster.Header` | Sticky-шапка (обычно показывается при выделении ≥1 элемента) | ✅ `ListMasterHeader-ai.md` |
|
|
38
|
+
| `ListMaster.Body` | Основная область с фильтрами и списком | — (тривиальная обёртка) |
|
|
39
|
+
| `ListMaster.ChipGroup` | Горизонтальная группа `Chip`-фильтров с фиксированным `oneLine` и отступами | — (описано ниже) |
|
|
40
|
+
| `ListMaster.Footer` | Sticky-футер с агрегатами и действиями | ✅ `ListMasterFooter-ai.md` |
|
|
41
|
+
| `ListMaster.FooterDescription` | Текстовая часть футера (например, «Сумма: 1 220 000 RUB») | — (тривиальная обёртка) |
|
|
42
|
+
| `ListMaster.FooterControls` | Контейнер для кнопок в футере | — (тривиальная обёртка) |
|
|
43
|
+
| `ListMaster.SelectionControls` | Flex-контейнер с `justify-content: space-between` для управления выделением (обычно: «Выбрать все» / «Выбрано: N» / «Сбросить») | — (описано ниже) |
|
|
115
44
|
|
|
116
45
|
---
|
|
117
46
|
|
|
118
|
-
##
|
|
119
|
-
|
|
120
|
-
### Scroll-компенсация в `ListMasterHeader`
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
containerHeightRef.current = containerRef.current.getBoundingClientRect().height;
|
|
125
|
-
window.scrollTo({ top: window.scrollY + containerHeightRef.current });
|
|
126
|
-
return () => window.scrollTo({ top: Math.max(window.scrollY - containerHeightRef.current, 0) });
|
|
127
|
-
}, []);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
- **При монтировании Header** (который обычно появляется при выборе элементов — `SelectionControls` mode) — страница "проскроллена" на высоту header'а, чтобы чекбоксы не "прыгнули" вниз.
|
|
131
|
-
- **При размонтировании** — обратный scroll.
|
|
132
|
-
- **Критичный side effect**: меняет `window.scrollY`. Если Header рендерится и пропадает часто — поведение видно. Этот UX-паттерн специфичен для случаев "выбрал строку → появился контрол-бар".
|
|
47
|
+
## Инварианты
|
|
133
48
|
|
|
134
|
-
|
|
49
|
+
- **`forwardRef`** — обязателен на `ListMaster`. Реализован через `Object.assign(forwardRef, { Body, Header, ... })`. Не убирать.
|
|
50
|
+
- **Статические свойства** (`Body`, `Header`, `ChipGroup`, `Footer`, `FooterControls`, `FooterDescription`, `SelectionControls`) — часть публичного API. Не переименовывать без подтверждения.
|
|
51
|
+
- **Все компоненты семейства `ListMaster*`** также экспортируются из barrel `index.ts` отдельно (можно использовать как `import { ListMasterHeader }`, так и `<ListMaster.Header>`). Сохраняй оба варианта.
|
|
52
|
+
- **Корневой DOM** — `<div>`. Не менять, ref-target — этот div.
|
|
135
53
|
|
|
136
|
-
|
|
54
|
+
---
|
|
137
55
|
|
|
138
|
-
|
|
56
|
+
## Связанные компоненты
|
|
139
57
|
|
|
140
|
-
|
|
141
|
-
<ChipGroup className={...} oneLine {...rest} ref={ref}>
|
|
142
|
-
```
|
|
58
|
+
### Описаны здесь (тривиальные обёртки без отдельного AI.md)
|
|
143
59
|
|
|
144
|
-
|
|
60
|
+
- **`ListMasterBody`** (`ListMaster.Body`) — `<div>`-контейнер для основной области; ничего кроме `className + ...rest` не делает. Семантика — «между Header и Footer».
|
|
61
|
+
- **`ListMasterChipGroup`** (`ListMaster.ChipGroup`) — обёртка над `<ChipGroup>` с фиксированным `oneLine={true}` и горизонтальным padding `0 16px`. Используется для горизонтальной полосы фильтров с прокруткой.
|
|
62
|
+
- **`ListMasterFooterDescription`** (`ListMaster.FooterDescription`) — `<div>` с `padding-right: 16px`. Левая текстовая часть футера.
|
|
63
|
+
- **`ListMasterFooterControls`** (`ListMaster.FooterControls`) — `<div>` с `white-space: nowrap`. Правая часть футера для кнопок.
|
|
64
|
+
- **`SelectionControls`** (`ListMaster.SelectionControls`) — `<div>` с `display: flex; justify-content: space-between; align-items: center`. Layout для controls внутри `ListMaster.Header`.
|
|
145
65
|
|
|
146
|
-
###
|
|
66
|
+
### Имеют отдельный AI.md
|
|
147
67
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
export const ListMaster: IListMasterFC = (...) => <div>...</div>;
|
|
151
|
-
ListMaster.Body = ListMasterBody;
|
|
152
|
-
// ...
|
|
153
|
-
```
|
|
68
|
+
- **`ListMasterHeader`** — sticky-шапка с компенсацией высоты через `window.scrollTo` (см. `ListMasterHeader-ai.md`).
|
|
69
|
+
- **`ListMasterFooter`** — sticky-футер (см. `ListMasterFooter-ai.md`).
|
|
154
70
|
|
|
155
|
-
|
|
71
|
+
### Внешние
|
|
156
72
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## Инварианты
|
|
160
|
-
|
|
161
|
-
- **`sticky=true` по умолчанию** на Header/Footer — публичный контракт.
|
|
162
|
-
- **Scroll-компенсация в Header** — UX-решение, не удалять без обсуждения (ломает visual continuity в scenario "выбор элементов").
|
|
163
|
-
- **`ListMasterChipGroup` зашитый `oneLine`** — контракт. Для многострочного — потребитель использует `ChipGroup` напрямую.
|
|
164
|
-
- **ForwardRef** на всех слотах.
|
|
165
|
-
- **Compound: `ListMaster.{Body,ChipGroup,Footer,FooterControls,FooterDescription,Header,SelectionControls}`** — публичный API.
|
|
166
|
-
- **Отсутствуют собственные токены** — визуал наследуется от `ChipGroup` и общих CSS-переменных.
|
|
167
|
-
- **Нет stories/тестов** — compound тонкий, покрытие лежит на демо-приложении. При добавлении новых слотов **обязательно** написать story.
|
|
73
|
+
- **`List`** — обычно содержится внутри `ListMaster.Body`.
|
|
74
|
+
- **`ChipGroup`** — используется внутри `ListMasterChipGroup` как реализация.
|
|
168
75
|
|
|
169
76
|
---
|
|
170
77
|
|
|
171
|
-
##
|
|
172
|
-
|
|
173
|
-
- `<div>`-контейнеры, без ARIA-ролей. Семантика — на совести потребителя (добавь `role="region"` + `aria-label` через `...rest` для скринридеров).
|
|
174
|
-
- Sticky Header не ломает фокусируемость (CSS, не portal).
|
|
175
|
-
- **Scroll-переход в Header при mount** — может сбивать скринридер на слабых устройствах (меняется позиция scroll'а). Не отключается через prop.
|
|
176
|
-
|
|
177
|
-
---
|
|
78
|
+
## Stories
|
|
178
79
|
|
|
179
|
-
|
|
80
|
+
Основные истории: `stories/ListMaster/ListMaster.stories.tsx`
|
|
81
|
+
Файлы примеров: `stories/ListMaster/examples/`
|
|
180
82
|
|
|
181
|
-
|
|
83
|
+
| Story | Example file | Что демонстрирует |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `Default` | `Default.tsx` | Production-like пример: TabsLine + `ChipGroup`-фильтры + selectable `ListTableItem` + sticky Header при выделении + sticky Footer с суммой и действиями + LightBox с расширенными фильтрами + пустое состояние |
|
|
182
86
|
|
|
183
87
|
---
|
|
184
88
|
|
|
185
|
-
##
|
|
89
|
+
## История изменений
|
|
186
90
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
- `
|
|
190
|
-
- `Pagination` — типично в `.FooterControls`.
|
|
91
|
+
| Дата | Изменение |
|
|
92
|
+
|---|---|
|
|
93
|
+
| 2026-04-29 | Создан документ. Добавлен `forwardRef` на `ListMaster` (breaking — изменён TS-тип, удалён `IListMasterFC`). Исправлен `displayName` у `ListMasterChipGroup`. Экспортирован `IListMasterChipGroupProps`. Добавлены unit-тесты. |
|
|
191
94
|
|
|
192
95
|
---
|
|
193
96
|
|
|
194
|
-
##
|
|
97
|
+
## Примеры использования
|
|
195
98
|
|
|
196
|
-
|
|
99
|
+
### Default
|
|
197
100
|
|
|
198
|
-
|
|
101
|
+
`stories/ListMaster/examples/Default.tsx`
|
|
199
102
|
|
|
200
|
-
|
|
103
|
+
```tsx
|
|
104
|
+
import React from "react";
|
|
105
|
+
import {
|
|
106
|
+
Button,
|
|
107
|
+
ButtonDropdown,
|
|
108
|
+
Checkbox,
|
|
109
|
+
CheckboxYGroup,
|
|
110
|
+
ChipDatePicker,
|
|
111
|
+
ChipGroup,
|
|
112
|
+
ChipMultiselect,
|
|
113
|
+
ChipOptions,
|
|
114
|
+
ChipSort,
|
|
115
|
+
ChipSuggest,
|
|
116
|
+
EBodyPageType,
|
|
117
|
+
EButtonDotsTheme,
|
|
118
|
+
EButtonTheme,
|
|
119
|
+
EComponentSize,
|
|
120
|
+
EFontType,
|
|
121
|
+
EFontWeightText,
|
|
122
|
+
EFooterPageType,
|
|
123
|
+
EFormFieldStatus,
|
|
124
|
+
EHeaderPageType,
|
|
125
|
+
EMarkerStatus,
|
|
126
|
+
ETextSize,
|
|
127
|
+
ETitleSize,
|
|
128
|
+
Gap,
|
|
129
|
+
ISelectFieldOption,
|
|
130
|
+
ISuggestFieldOption,
|
|
131
|
+
LightBox,
|
|
132
|
+
List,
|
|
133
|
+
ListEmptyState,
|
|
134
|
+
ListItemControlsButton,
|
|
135
|
+
ListItemControlsButtonDropdown,
|
|
136
|
+
ListTableItem,
|
|
137
|
+
ListMaster,
|
|
138
|
+
MarkerStatus,
|
|
139
|
+
MultiselectField,
|
|
140
|
+
Page,
|
|
141
|
+
TabsLine,
|
|
142
|
+
Text,
|
|
143
|
+
Title,
|
|
144
|
+
} from "@sberbusiness/triplex-next";
|
|
145
|
+
import {
|
|
146
|
+
AttachmentStrokeSrvIcon20,
|
|
147
|
+
DotshorizontalStrokeSrvIcon20,
|
|
148
|
+
EmptytableSysIcon96,
|
|
149
|
+
} from "@sberbusiness/icons-next";
|
|
150
|
+
|
|
151
|
+
type TTabId = "all" | "draft" | "sign" | "executed" | "rejected";
|
|
152
|
+
type TItemStatus = "draft" | "signed" | "executed" | "rejected";
|
|
153
|
+
|
|
154
|
+
const tabsLine: Array<{ id: TTabId; label: string; showNotificationIcon?: boolean }> = [
|
|
155
|
+
{ id: "all", label: "Все" },
|
|
156
|
+
{ id: "draft", label: "Черновики", showNotificationIcon: true },
|
|
157
|
+
{ id: "sign", label: "На подпись и отправку" },
|
|
158
|
+
{ id: "executed", label: "Исполненные" },
|
|
159
|
+
{ id: "rejected", label: "Отклоненные" },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const chipSortOptions: ISelectFieldOption[] = [
|
|
163
|
+
{ id: "list-master-chip-sort-1", label: "По возрастанию суммы", value: "asc" },
|
|
164
|
+
{ id: "list-master-chip-sort-2", label: "По убыванию суммы", value: "desc" },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const allStatuses: TItemStatus[] = ["draft", "signed", "executed", "rejected"];
|
|
168
|
+
|
|
169
|
+
const statusOptions: Array<{ id: string; label: string; value: TItemStatus }> = [
|
|
170
|
+
{ id: "status-draft", label: "Черновики", value: "draft" },
|
|
171
|
+
{ id: "status-signed", label: "На подпись", value: "signed" },
|
|
172
|
+
{ id: "status-executed", label: "Исполненные", value: "executed" },
|
|
173
|
+
{ id: "status-rejected", label: "Отклоненные", value: "rejected" },
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const statusToMarker: Record<TItemStatus, EMarkerStatus> = {
|
|
177
|
+
draft: EMarkerStatus.WAITING,
|
|
178
|
+
signed: EMarkerStatus.SUCCESS,
|
|
179
|
+
executed: EMarkerStatus.SUCCESS,
|
|
180
|
+
rejected: EMarkerStatus.ERROR,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const initialAccountOptions: ISuggestFieldOption[] = [
|
|
184
|
+
{ id: "acc-1", label: "Счет 40702 810..." },
|
|
185
|
+
{ id: "acc-2", label: "Счет 40702 999..." },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
const initialCounterpartyOptions: ISuggestFieldOption[] = [
|
|
189
|
+
{ id: "cp-1", label: "ООО Голубая Роза" },
|
|
190
|
+
{ id: "cp-2", label: "ООО Лунная Лилия" },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
interface IListItemData {
|
|
194
|
+
id: string;
|
|
195
|
+
amount: number;
|
|
196
|
+
title: string;
|
|
197
|
+
description: string;
|
|
198
|
+
numberAndDate: string;
|
|
199
|
+
paymentDate: string;
|
|
200
|
+
status: TItemStatus;
|
|
201
|
+
statusText: string;
|
|
202
|
+
accountId: string;
|
|
203
|
+
counterpartyId: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const listItemsData: IListItemData[] = [
|
|
207
|
+
{
|
|
208
|
+
id: "1",
|
|
209
|
+
amount: 1_220_000_000,
|
|
210
|
+
title: "№1 ООО Голубая Роза Голубая",
|
|
211
|
+
description: "НДС не облагается",
|
|
212
|
+
numberAndDate: "40702 810 2 0527 5000000 от 09.04.24",
|
|
213
|
+
paymentDate: "20240409",
|
|
214
|
+
status: "draft",
|
|
215
|
+
statusText: "Черновик",
|
|
216
|
+
accountId: "acc-1",
|
|
217
|
+
counterpartyId: "cp-1",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "2",
|
|
221
|
+
amount: 85_500_000,
|
|
222
|
+
title: "№2 ООО Лунная Лилия",
|
|
223
|
+
description: "НДС не облагается",
|
|
224
|
+
numberAndDate: "40702 810 2 0527 5000001 от 21.05.24",
|
|
225
|
+
paymentDate: "20240521",
|
|
226
|
+
status: "signed",
|
|
227
|
+
statusText: "На подпись",
|
|
228
|
+
accountId: "acc-1",
|
|
229
|
+
counterpartyId: "cp-2",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "3",
|
|
233
|
+
amount: 3_300_000,
|
|
234
|
+
title: "№3 ООО Голубая Роза",
|
|
235
|
+
description: "НДС не облагается",
|
|
236
|
+
numberAndDate: "40702 810 2 0527 5000002 от 02.06.24",
|
|
237
|
+
paymentDate: "20240602",
|
|
238
|
+
status: "executed",
|
|
239
|
+
statusText: "Исполнено",
|
|
240
|
+
accountId: "acc-2",
|
|
241
|
+
counterpartyId: "cp-1",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "4",
|
|
245
|
+
amount: 12_700_000,
|
|
246
|
+
title: "№4 ООО Лунная Лилия",
|
|
247
|
+
description: "НДС не облагается",
|
|
248
|
+
numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
|
|
249
|
+
paymentDate: "20240710",
|
|
250
|
+
status: "rejected",
|
|
251
|
+
statusText: "Отклонено",
|
|
252
|
+
accountId: "acc-2",
|
|
253
|
+
counterpartyId: "cp-2",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "5",
|
|
257
|
+
amount: 12_700_000,
|
|
258
|
+
title: "№4 ООО Лунная Лилия",
|
|
259
|
+
description: "НДС не облагается",
|
|
260
|
+
numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
|
|
261
|
+
paymentDate: "20240710",
|
|
262
|
+
status: "rejected",
|
|
263
|
+
statusText: "Отклонено",
|
|
264
|
+
accountId: "acc-2",
|
|
265
|
+
counterpartyId: "cp-2",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "6",
|
|
269
|
+
amount: 12_700_000,
|
|
270
|
+
title: "№4 ООО Лунная Лилия",
|
|
271
|
+
description: "НДС не облагается",
|
|
272
|
+
numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
|
|
273
|
+
paymentDate: "20240710",
|
|
274
|
+
status: "rejected",
|
|
275
|
+
statusText: "Отклонено",
|
|
276
|
+
accountId: "acc-2",
|
|
277
|
+
counterpartyId: "cp-2",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "7",
|
|
281
|
+
amount: 12_700_000,
|
|
282
|
+
title: "№4 ООО Лунная Лилия",
|
|
283
|
+
description: "НДС не облагается",
|
|
284
|
+
numberAndDate: "40702 999 1 0527 5000003 от 10.07.24",
|
|
285
|
+
paymentDate: "20240710",
|
|
286
|
+
status: "rejected",
|
|
287
|
+
statusText: "Отклонено",
|
|
288
|
+
accountId: "acc-2",
|
|
289
|
+
counterpartyId: "cp-2",
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
export const Default = () => {
|
|
294
|
+
const [chipSortValue, setChipSortValue] = React.useState<ISelectFieldOption>(chipSortOptions[0]);
|
|
295
|
+
const [selectedListItemIds, setSelectedListItemIds] = React.useState<string[]>([]);
|
|
296
|
+
const [selectedTabLineId, setSelectedTabLineId] = React.useState<TTabId>("all");
|
|
297
|
+
|
|
298
|
+
const [accountValue, setAccountValue] = React.useState<ISuggestFieldOption | undefined>(undefined);
|
|
299
|
+
const [accountOptions, setAccountOptions] = React.useState<ISuggestFieldOption[]>([]);
|
|
300
|
+
const [accountTooltipOpen, setAccountTooltipOpen] = React.useState(false);
|
|
301
|
+
const accountInitialOptionsRef = React.useRef<ISuggestFieldOption[]>(initialAccountOptions);
|
|
302
|
+
|
|
303
|
+
const [counterpartyValue, setCounterpartyValue] = React.useState<ISuggestFieldOption | undefined>(undefined);
|
|
304
|
+
const [counterpartyOptions, setCounterpartyOptions] = React.useState<ISuggestFieldOption[]>([]);
|
|
305
|
+
const [counterpartyTooltipOpen, setCounterpartyTooltipOpen] = React.useState(false);
|
|
306
|
+
const counterpartyInitialOptionsRef = React.useRef<ISuggestFieldOption[]>(initialCounterpartyOptions);
|
|
307
|
+
|
|
308
|
+
const [multiselectStatusValues, setMultiselectStatusValues] = React.useState<TItemStatus[]>(allStatuses);
|
|
309
|
+
|
|
310
|
+
const [dateFrom, setDateFrom] = React.useState("");
|
|
311
|
+
const [dateTo, setDateTo] = React.useState("");
|
|
312
|
+
|
|
313
|
+
const [openLightBoxOptions, setOpenLightBoxOptions] = React.useState(false);
|
|
314
|
+
|
|
315
|
+
const clearFilters = () => {
|
|
316
|
+
setAccountValue(undefined);
|
|
317
|
+
setCounterpartyValue(undefined);
|
|
318
|
+
setMultiselectStatusValues(allStatuses);
|
|
319
|
+
setDateFrom("");
|
|
320
|
+
setDateTo("");
|
|
321
|
+
setSelectedListItemIds([]);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const changedFiltersCount =
|
|
325
|
+
(accountValue ? 1 : 0) +
|
|
326
|
+
(counterpartyValue ? 1 : 0) +
|
|
327
|
+
(multiselectStatusValues.length !== allStatuses.length ? 1 : 0) +
|
|
328
|
+
(dateFrom ? 1 : 0) +
|
|
329
|
+
(dateTo ? 1 : 0);
|
|
330
|
+
|
|
331
|
+
const filteredItems = React.useMemo(() => {
|
|
332
|
+
let next = [...listItemsData];
|
|
333
|
+
|
|
334
|
+
if (selectedTabLineId !== "all") {
|
|
335
|
+
const map: Record<Exclude<TTabId, "all">, TItemStatus> = {
|
|
336
|
+
draft: "draft",
|
|
337
|
+
sign: "signed",
|
|
338
|
+
executed: "executed",
|
|
339
|
+
rejected: "rejected",
|
|
340
|
+
};
|
|
341
|
+
next = next.filter((i) => i.status === map[selectedTabLineId as Exclude<TTabId, "all">]);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (multiselectStatusValues.length !== allStatuses.length) {
|
|
345
|
+
next = next.filter((i) => multiselectStatusValues.includes(i.status));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (accountValue) next = next.filter((i) => i.accountId === accountValue.id);
|
|
349
|
+
if (counterpartyValue) next = next.filter((i) => i.counterpartyId === counterpartyValue.id);
|
|
350
|
+
|
|
351
|
+
if (dateFrom) {
|
|
352
|
+
next = next.filter((i) => i.paymentDate >= dateFrom);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (dateTo) {
|
|
356
|
+
next = next.filter((i) => i.paymentDate <= dateTo);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return next;
|
|
360
|
+
}, [dateFrom, dateTo, multiselectStatusValues, accountValue, counterpartyValue, selectedTabLineId]);
|
|
361
|
+
|
|
362
|
+
const sortedItems = React.useMemo(() => {
|
|
363
|
+
const next = [...filteredItems];
|
|
364
|
+
next.sort((a, b) => (chipSortValue.value === "desc" ? b.amount - a.amount : a.amount - b.amount));
|
|
365
|
+
return next;
|
|
366
|
+
}, [filteredItems, chipSortValue]);
|
|
367
|
+
|
|
368
|
+
const sumSelected = React.useMemo(() => {
|
|
369
|
+
return listItemsData
|
|
370
|
+
.filter((item) => selectedListItemIds.includes(item.id))
|
|
371
|
+
.reduce((acc, item) => acc + item.amount, 0);
|
|
372
|
+
}, [selectedListItemIds]);
|
|
373
|
+
|
|
374
|
+
const handleSelectListItem = (id: string, selected: boolean) => {
|
|
375
|
+
setSelectedListItemIds((prev) => (selected ? [...prev, id] : prev.filter((itemId) => itemId !== id)));
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const selectAllListItems = () => {
|
|
379
|
+
setSelectedListItemIds(sortedItems.map((i) => i.id));
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const clearSelected = () => setSelectedListItemIds([]);
|
|
383
|
+
|
|
384
|
+
const itemActions = [
|
|
385
|
+
{ id: "action-1", label: "Действие 1", onSelect: () => alert("Действие 1") },
|
|
386
|
+
{ id: "action-2", label: "Действие 2", onSelect: () => alert("Действие 2") },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const options = [
|
|
390
|
+
{
|
|
391
|
+
id: "button-dropdown-card-with-selectable-option-1",
|
|
392
|
+
label: "Текст пункта меню 1",
|
|
393
|
+
onSelect: () => alert("Выбран пункт меню 1."),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "button-dropdown-card-with-selectable-option-2",
|
|
397
|
+
label: "Текст пункта меню 2",
|
|
398
|
+
onSelect: () => alert("Выбран пункт меню 2."),
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "button-dropdown-card-with-selectable-option-3",
|
|
402
|
+
label: "Текст пункта меню 3",
|
|
403
|
+
onSelect: () => alert("Выбран пункт меню 3."),
|
|
404
|
+
},
|
|
405
|
+
];
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<div>
|
|
409
|
+
<ListMaster>
|
|
410
|
+
{selectedListItemIds.length ? (
|
|
411
|
+
<ListMaster.Header>
|
|
412
|
+
<ListMaster.SelectionControls>
|
|
413
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={selectAllListItems}>
|
|
414
|
+
Выбрать все
|
|
415
|
+
</Button>
|
|
416
|
+
|
|
417
|
+
<Text size={ETextSize.B3} weight={EFontWeightText.SEMIBOLD}>
|
|
418
|
+
Выбрано: {selectedListItemIds.length}
|
|
419
|
+
</Text>
|
|
420
|
+
|
|
421
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD} onClick={clearSelected}>
|
|
422
|
+
Сбросить все
|
|
423
|
+
</Button>
|
|
424
|
+
</ListMaster.SelectionControls>
|
|
425
|
+
</ListMaster.Header>
|
|
426
|
+
) : null}
|
|
427
|
+
|
|
428
|
+
<ListMaster.Body>
|
|
429
|
+
<Gap size={16} />
|
|
430
|
+
|
|
431
|
+
<TabsLine
|
|
432
|
+
tabs={tabsLine}
|
|
433
|
+
selectedId={selectedTabLineId}
|
|
434
|
+
onChangeTab={(tabId) => {
|
|
435
|
+
setSelectedTabLineId(tabId as TTabId);
|
|
436
|
+
setSelectedListItemIds([]);
|
|
437
|
+
}}
|
|
438
|
+
paddingX={16}
|
|
439
|
+
withSeparator
|
|
440
|
+
/>
|
|
441
|
+
|
|
442
|
+
<Gap size={16} />
|
|
443
|
+
|
|
444
|
+
<ListMaster.ChipGroup>
|
|
445
|
+
<ChipSort
|
|
446
|
+
label="Сортировка"
|
|
447
|
+
defaultValue={chipSortOptions[0]}
|
|
448
|
+
value={chipSortValue}
|
|
449
|
+
options={chipSortOptions}
|
|
450
|
+
onChange={setChipSortValue}
|
|
451
|
+
size={EComponentSize.MD}
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
<ChipOptions
|
|
455
|
+
selected={changedFiltersCount > 0}
|
|
456
|
+
clearSelected={clearFilters}
|
|
457
|
+
onClick={() => setOpenLightBoxOptions(true)}
|
|
458
|
+
size={EComponentSize.MD}
|
|
459
|
+
>
|
|
460
|
+
{changedFiltersCount > 0 ? changedFiltersCount : undefined}
|
|
461
|
+
</ChipOptions>
|
|
462
|
+
|
|
463
|
+
<ChipSuggest
|
|
464
|
+
size={EComponentSize.MD}
|
|
465
|
+
label="Счет"
|
|
466
|
+
placeholder="Выберите счет"
|
|
467
|
+
noOptionsText="Ничего не найдено"
|
|
468
|
+
value={accountValue}
|
|
469
|
+
options={accountOptions}
|
|
470
|
+
tooltipOpen={accountTooltipOpen}
|
|
471
|
+
onSelect={(value) => {
|
|
472
|
+
setAccountValue(value);
|
|
473
|
+
setSelectedListItemIds([]);
|
|
474
|
+
}}
|
|
475
|
+
onFilter={(input) => {
|
|
476
|
+
if (!input) {
|
|
477
|
+
const initial = accountInitialOptionsRef.current;
|
|
478
|
+
setAccountOptions(initial);
|
|
479
|
+
setAccountTooltipOpen(false);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const filtered = accountInitialOptionsRef.current.filter((o) =>
|
|
483
|
+
o.label.toLowerCase().includes(input.toLowerCase()),
|
|
484
|
+
);
|
|
485
|
+
setAccountOptions(filtered);
|
|
486
|
+
setAccountTooltipOpen(filtered.length === 0);
|
|
487
|
+
}}
|
|
488
|
+
targetProps={{
|
|
489
|
+
clearSelected: () => {
|
|
490
|
+
setAccountValue(undefined);
|
|
491
|
+
setSelectedListItemIds([]);
|
|
492
|
+
},
|
|
493
|
+
}}
|
|
494
|
+
dropdownProps={{
|
|
495
|
+
onOpen: () => {
|
|
496
|
+
setAccountOptions(accountInitialOptionsRef.current);
|
|
497
|
+
setAccountTooltipOpen(false);
|
|
498
|
+
},
|
|
499
|
+
}}
|
|
500
|
+
/>
|
|
501
|
+
|
|
502
|
+
<ChipSuggest
|
|
503
|
+
size={EComponentSize.MD}
|
|
504
|
+
label="Контрагент"
|
|
505
|
+
placeholder="Выберите контрагента"
|
|
506
|
+
noOptionsText="Ничего не найдено"
|
|
507
|
+
value={counterpartyValue}
|
|
508
|
+
options={counterpartyOptions}
|
|
509
|
+
tooltipOpen={counterpartyTooltipOpen}
|
|
510
|
+
onSelect={(value) => {
|
|
511
|
+
setCounterpartyValue(value);
|
|
512
|
+
setSelectedListItemIds([]);
|
|
513
|
+
}}
|
|
514
|
+
onFilter={(input) => {
|
|
515
|
+
if (!input) {
|
|
516
|
+
const initial = counterpartyInitialOptionsRef.current;
|
|
517
|
+
setCounterpartyOptions(initial);
|
|
518
|
+
setCounterpartyTooltipOpen(false);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const filtered = counterpartyInitialOptionsRef.current.filter((o) =>
|
|
522
|
+
o.label.toLowerCase().includes(input.toLowerCase()),
|
|
523
|
+
);
|
|
524
|
+
setCounterpartyOptions(filtered);
|
|
525
|
+
setCounterpartyTooltipOpen(filtered.length === 0);
|
|
526
|
+
}}
|
|
527
|
+
targetProps={{
|
|
528
|
+
clearSelected: () => {
|
|
529
|
+
setCounterpartyValue(undefined);
|
|
530
|
+
setSelectedListItemIds([]);
|
|
531
|
+
},
|
|
532
|
+
}}
|
|
533
|
+
dropdownProps={{
|
|
534
|
+
onOpen: () => {
|
|
535
|
+
setCounterpartyOptions(counterpartyInitialOptionsRef.current);
|
|
536
|
+
setCounterpartyTooltipOpen(false);
|
|
537
|
+
},
|
|
538
|
+
}}
|
|
539
|
+
/>
|
|
540
|
+
|
|
541
|
+
<ChipMultiselect
|
|
542
|
+
clearSelected={() => {
|
|
543
|
+
setMultiselectStatusValues(allStatuses);
|
|
544
|
+
setSelectedListItemIds([]);
|
|
545
|
+
}}
|
|
546
|
+
selected={multiselectStatusValues.length !== allStatuses.length}
|
|
547
|
+
label="Статус"
|
|
548
|
+
displayedValue={`${multiselectStatusValues.length}`}
|
|
549
|
+
size={EComponentSize.MD}
|
|
550
|
+
>
|
|
551
|
+
{({ opened, setOpened, targetRef, dropdownRef }) => (
|
|
552
|
+
<MultiselectField.Dropdown
|
|
553
|
+
opened={opened}
|
|
554
|
+
setOpened={setOpened}
|
|
555
|
+
targetRef={targetRef}
|
|
556
|
+
ref={dropdownRef}
|
|
557
|
+
>
|
|
558
|
+
<MultiselectField.Dropdown.Content>
|
|
559
|
+
<div
|
|
560
|
+
style={{
|
|
561
|
+
display: "flex",
|
|
562
|
+
flexDirection: "column",
|
|
563
|
+
gap: 8,
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
{statusOptions.map((opt) => {
|
|
567
|
+
const checked = multiselectStatusValues.includes(opt.value);
|
|
568
|
+
return (
|
|
569
|
+
<CheckboxYGroup key={opt.id}>
|
|
570
|
+
<Checkbox
|
|
571
|
+
checked={checked}
|
|
572
|
+
onChange={(e) => {
|
|
573
|
+
const nextChecked = e.target.checked;
|
|
574
|
+
setMultiselectStatusValues((prev) => {
|
|
575
|
+
if (nextChecked) {
|
|
576
|
+
if (prev.includes(opt.value)) return prev;
|
|
577
|
+
return [...prev, opt.value];
|
|
578
|
+
}
|
|
579
|
+
return prev.filter((v) => v !== opt.value);
|
|
580
|
+
});
|
|
581
|
+
setSelectedListItemIds([]);
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
584
|
+
{opt.label}
|
|
585
|
+
</Checkbox>
|
|
586
|
+
</CheckboxYGroup>
|
|
587
|
+
);
|
|
588
|
+
})}
|
|
589
|
+
</div>
|
|
590
|
+
</MultiselectField.Dropdown.Content>
|
|
591
|
+
</MultiselectField.Dropdown>
|
|
592
|
+
)}
|
|
593
|
+
</ChipMultiselect>
|
|
594
|
+
|
|
595
|
+
<ChipDatePicker
|
|
596
|
+
value={dateFrom}
|
|
597
|
+
label="Дата с"
|
|
598
|
+
onChange={(value) => {
|
|
599
|
+
setDateFrom(value);
|
|
600
|
+
setSelectedListItemIds([]);
|
|
601
|
+
}}
|
|
602
|
+
size={EComponentSize.MD}
|
|
603
|
+
status={EFormFieldStatus.DEFAULT}
|
|
604
|
+
/>
|
|
605
|
+
|
|
606
|
+
<ChipDatePicker
|
|
607
|
+
value={dateTo}
|
|
608
|
+
label="Дата по"
|
|
609
|
+
onChange={(value) => {
|
|
610
|
+
setDateTo(value);
|
|
611
|
+
setSelectedListItemIds([]);
|
|
612
|
+
}}
|
|
613
|
+
size={EComponentSize.MD}
|
|
614
|
+
status={EFormFieldStatus.DEFAULT}
|
|
615
|
+
/>
|
|
616
|
+
</ListMaster.ChipGroup>
|
|
617
|
+
|
|
618
|
+
<Gap size={12} />
|
|
619
|
+
|
|
620
|
+
{sortedItems.length ? (
|
|
621
|
+
<List>
|
|
622
|
+
{sortedItems.map((item) => (
|
|
623
|
+
<ListTableItem
|
|
624
|
+
key={item.id}
|
|
625
|
+
selected={selectedListItemIds.includes(item.id)}
|
|
626
|
+
onSelect={(sel) => handleSelectListItem(item.id, sel)}
|
|
627
|
+
onClickItem={() => console.log("click", item.id)}
|
|
628
|
+
controlButtons={
|
|
629
|
+
<>
|
|
630
|
+
<ListItemControlsButton
|
|
631
|
+
icon={<AttachmentStrokeSrvIcon20 paletteIndex={5} />}
|
|
632
|
+
>
|
|
633
|
+
Скачать
|
|
634
|
+
</ListItemControlsButton>
|
|
635
|
+
|
|
636
|
+
<ListItemControlsButtonDropdown
|
|
637
|
+
icon={<DotshorizontalStrokeSrvIcon20 paletteIndex={5} />}
|
|
638
|
+
options={itemActions}
|
|
639
|
+
>
|
|
640
|
+
Действия
|
|
641
|
+
</ListItemControlsButtonDropdown>
|
|
642
|
+
</>
|
|
643
|
+
}
|
|
644
|
+
>
|
|
645
|
+
<Text size={ETextSize.B2} weight={EFontWeightText.SEMIBOLD}>
|
|
646
|
+
{item.amount.toLocaleString("ru-RU")} RUB
|
|
647
|
+
</Text>
|
|
648
|
+
<Text size={ETextSize.B3} tag="div">
|
|
649
|
+
{item.title}
|
|
650
|
+
</Text>
|
|
651
|
+
<Text size={ETextSize.B3} type={EFontType.SECONDARY} tag="div">
|
|
652
|
+
{item.description}
|
|
653
|
+
</Text>
|
|
654
|
+
<Text size={ETextSize.B3} type={EFontType.SECONDARY} tag="div">
|
|
655
|
+
{item.numberAndDate}
|
|
656
|
+
</Text>
|
|
657
|
+
|
|
658
|
+
<MarkerStatus status={statusToMarker[item.status]} size={EComponentSize.LG}>
|
|
659
|
+
{item.statusText}
|
|
660
|
+
</MarkerStatus>
|
|
661
|
+
</ListTableItem>
|
|
662
|
+
))}
|
|
663
|
+
</List>
|
|
664
|
+
) : (
|
|
665
|
+
<ListEmptyState>
|
|
666
|
+
<EmptytableSysIcon96 />
|
|
667
|
+
|
|
668
|
+
<Gap size={8} />
|
|
669
|
+
|
|
670
|
+
<Text size={ETextSize.B3}>Ничего не найдено. Попробуйте выбрать другие фильтры.</Text>
|
|
671
|
+
|
|
672
|
+
<Gap size={16} />
|
|
673
|
+
|
|
674
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM} onClick={clearFilters}>
|
|
675
|
+
Действие
|
|
676
|
+
</Button>
|
|
677
|
+
|
|
678
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM} onClick={clearFilters}>
|
|
679
|
+
Сбросить фильтры
|
|
680
|
+
</Button>
|
|
681
|
+
</ListEmptyState>
|
|
682
|
+
)}
|
|
683
|
+
</ListMaster.Body>
|
|
684
|
+
|
|
685
|
+
{selectedListItemIds.length ? (
|
|
686
|
+
<ListMaster.Footer>
|
|
687
|
+
<ListMaster.FooterDescription>
|
|
688
|
+
<Text size={ETextSize.B3} weight={EFontWeightText.SEMIBOLD}>
|
|
689
|
+
Сумма:
|
|
690
|
+
<br />
|
|
691
|
+
{sumSelected.toLocaleString("ru-RU")} RUB
|
|
692
|
+
</Text>
|
|
693
|
+
</ListMaster.FooterDescription>
|
|
694
|
+
|
|
695
|
+
<ListMaster.FooterControls>
|
|
696
|
+
<ButtonDropdown
|
|
697
|
+
theme={EButtonDotsTheme.DOTS_SECONDARY}
|
|
698
|
+
size={EComponentSize.MD}
|
|
699
|
+
options={options}
|
|
700
|
+
/>
|
|
701
|
+
|
|
702
|
+
<Button theme={EButtonTheme.GENERAL} size={EComponentSize.MD}>
|
|
703
|
+
Действие
|
|
704
|
+
</Button>
|
|
705
|
+
</ListMaster.FooterControls>
|
|
706
|
+
</ListMaster.Footer>
|
|
707
|
+
) : null}
|
|
708
|
+
</ListMaster>
|
|
709
|
+
|
|
710
|
+
{openLightBoxOptions ? (
|
|
711
|
+
<LightBox>
|
|
712
|
+
<LightBox.Content>
|
|
713
|
+
<Page>
|
|
714
|
+
<Page.Header type={EHeaderPageType.FIRST}>
|
|
715
|
+
<Page.Header.Title>
|
|
716
|
+
<Page.Header.Title.Content>
|
|
717
|
+
<Title size={ETitleSize.H2} tag="div">
|
|
718
|
+
Фильтры: {changedFiltersCount}
|
|
719
|
+
</Title>
|
|
720
|
+
</Page.Header.Title.Content>
|
|
721
|
+
</Page.Header.Title>
|
|
722
|
+
</Page.Header>
|
|
723
|
+
|
|
724
|
+
<Page.Body type={EBodyPageType.FIRST}>
|
|
725
|
+
<Text size={ETextSize.B3}>Плательщик и Получатель</Text>
|
|
726
|
+
|
|
727
|
+
<Gap size={16} />
|
|
728
|
+
|
|
729
|
+
<ChipGroup>
|
|
730
|
+
<ChipSuggest
|
|
731
|
+
size={EComponentSize.MD}
|
|
732
|
+
label="Счет"
|
|
733
|
+
placeholder="Выберите счет"
|
|
734
|
+
noOptionsText="Ничего не найдено"
|
|
735
|
+
value={accountValue}
|
|
736
|
+
options={accountOptions}
|
|
737
|
+
tooltipOpen={accountTooltipOpen}
|
|
738
|
+
onSelect={(value) => {
|
|
739
|
+
setAccountValue(value);
|
|
740
|
+
setSelectedListItemIds([]);
|
|
741
|
+
}}
|
|
742
|
+
onFilter={(input) => {
|
|
743
|
+
if (!input) {
|
|
744
|
+
const initial = accountInitialOptionsRef.current;
|
|
745
|
+
setAccountOptions(initial);
|
|
746
|
+
setAccountTooltipOpen(false);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const filtered = accountInitialOptionsRef.current.filter((o) =>
|
|
750
|
+
o.label.toLowerCase().includes(input.toLowerCase()),
|
|
751
|
+
);
|
|
752
|
+
setAccountOptions(filtered);
|
|
753
|
+
setAccountTooltipOpen(filtered.length === 0);
|
|
754
|
+
}}
|
|
755
|
+
targetProps={{
|
|
756
|
+
clearSelected: () => {
|
|
757
|
+
setAccountValue(undefined);
|
|
758
|
+
setSelectedListItemIds([]);
|
|
759
|
+
},
|
|
760
|
+
}}
|
|
761
|
+
dropdownProps={{
|
|
762
|
+
onOpen: () => {
|
|
763
|
+
setAccountOptions(accountInitialOptionsRef.current);
|
|
764
|
+
setAccountTooltipOpen(false);
|
|
765
|
+
},
|
|
766
|
+
}}
|
|
767
|
+
/>
|
|
768
|
+
|
|
769
|
+
<ChipSuggest
|
|
770
|
+
size={EComponentSize.MD}
|
|
771
|
+
label="Контрагент"
|
|
772
|
+
placeholder="Выберите контрагента"
|
|
773
|
+
noOptionsText="Ничего не найдено"
|
|
774
|
+
value={counterpartyValue}
|
|
775
|
+
options={counterpartyOptions}
|
|
776
|
+
tooltipOpen={counterpartyTooltipOpen}
|
|
777
|
+
onSelect={(value) => {
|
|
778
|
+
setCounterpartyValue(value);
|
|
779
|
+
setSelectedListItemIds([]);
|
|
780
|
+
}}
|
|
781
|
+
onFilter={(input) => {
|
|
782
|
+
if (!input) {
|
|
783
|
+
const initial = counterpartyInitialOptionsRef.current;
|
|
784
|
+
setCounterpartyOptions(initial);
|
|
785
|
+
setCounterpartyTooltipOpen(false);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const filtered = counterpartyInitialOptionsRef.current.filter((o) =>
|
|
789
|
+
o.label.toLowerCase().includes(input.toLowerCase()),
|
|
790
|
+
);
|
|
791
|
+
setCounterpartyOptions(filtered);
|
|
792
|
+
setCounterpartyTooltipOpen(filtered.length === 0);
|
|
793
|
+
}}
|
|
794
|
+
targetProps={{
|
|
795
|
+
clearSelected: () => {
|
|
796
|
+
setCounterpartyValue(undefined);
|
|
797
|
+
setSelectedListItemIds([]);
|
|
798
|
+
},
|
|
799
|
+
}}
|
|
800
|
+
dropdownProps={{
|
|
801
|
+
onOpen: () => {
|
|
802
|
+
setCounterpartyOptions(counterpartyInitialOptionsRef.current);
|
|
803
|
+
setCounterpartyTooltipOpen(false);
|
|
804
|
+
},
|
|
805
|
+
}}
|
|
806
|
+
/>
|
|
807
|
+
</ChipGroup>
|
|
808
|
+
|
|
809
|
+
<Gap size={24} />
|
|
810
|
+
|
|
811
|
+
<Text size={ETextSize.B3}>Статус</Text>
|
|
812
|
+
|
|
813
|
+
<Gap size={16} />
|
|
814
|
+
|
|
815
|
+
<ChipMultiselect
|
|
816
|
+
clearSelected={() => {
|
|
817
|
+
setMultiselectStatusValues(allStatuses);
|
|
818
|
+
setSelectedListItemIds([]);
|
|
819
|
+
}}
|
|
820
|
+
selected={multiselectStatusValues.length !== allStatuses.length}
|
|
821
|
+
label="Статус"
|
|
822
|
+
displayedValue={`${multiselectStatusValues.length}`}
|
|
823
|
+
size={EComponentSize.MD}
|
|
824
|
+
>
|
|
825
|
+
{({ opened, setOpened, targetRef, dropdownRef }) => (
|
|
826
|
+
<MultiselectField.Dropdown
|
|
827
|
+
opened={opened}
|
|
828
|
+
setOpened={setOpened}
|
|
829
|
+
targetRef={targetRef}
|
|
830
|
+
ref={dropdownRef}
|
|
831
|
+
>
|
|
832
|
+
<MultiselectField.Dropdown.Content>
|
|
833
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
834
|
+
{statusOptions.map((opt) => {
|
|
835
|
+
const checked = multiselectStatusValues.includes(opt.value);
|
|
836
|
+
return (
|
|
837
|
+
<Checkbox
|
|
838
|
+
key={opt.id}
|
|
839
|
+
checked={checked}
|
|
840
|
+
onChange={(e) => {
|
|
841
|
+
const nextChecked = e.target.checked;
|
|
842
|
+
setMultiselectStatusValues((prev) => {
|
|
843
|
+
if (nextChecked) {
|
|
844
|
+
if (prev.includes(opt.value)) return prev;
|
|
845
|
+
return [...prev, opt.value];
|
|
846
|
+
}
|
|
847
|
+
return prev.filter((v) => v !== opt.value);
|
|
848
|
+
});
|
|
849
|
+
setSelectedListItemIds([]);
|
|
850
|
+
}}
|
|
851
|
+
>
|
|
852
|
+
{opt.label}
|
|
853
|
+
</Checkbox>
|
|
854
|
+
);
|
|
855
|
+
})}
|
|
856
|
+
</div>
|
|
857
|
+
</MultiselectField.Dropdown.Content>
|
|
858
|
+
</MultiselectField.Dropdown>
|
|
859
|
+
)}
|
|
860
|
+
</ChipMultiselect>
|
|
861
|
+
|
|
862
|
+
<Gap size={24} />
|
|
863
|
+
|
|
864
|
+
<Text size={ETextSize.B3}>Дата документа</Text>
|
|
865
|
+
|
|
866
|
+
<Gap size={16} />
|
|
867
|
+
|
|
868
|
+
<ChipGroup>
|
|
869
|
+
<ChipDatePicker
|
|
870
|
+
value={dateFrom}
|
|
871
|
+
label="Дата с"
|
|
872
|
+
onChange={setDateFrom}
|
|
873
|
+
size={EComponentSize.MD}
|
|
874
|
+
status={EFormFieldStatus.DEFAULT}
|
|
875
|
+
/>
|
|
876
|
+
|
|
877
|
+
<ChipDatePicker
|
|
878
|
+
value={dateTo}
|
|
879
|
+
label="Дата по"
|
|
880
|
+
onChange={setDateTo}
|
|
881
|
+
size={EComponentSize.MD}
|
|
882
|
+
status={EFormFieldStatus.DEFAULT}
|
|
883
|
+
/>
|
|
884
|
+
</ChipGroup>
|
|
885
|
+
</Page.Body>
|
|
886
|
+
|
|
887
|
+
<Page.Footer type={EFooterPageType.FIRST}>
|
|
888
|
+
<Page.Footer.Description>
|
|
889
|
+
<Page.Footer.Description.Controls>
|
|
890
|
+
<Button
|
|
891
|
+
theme={EButtonTheme.SECONDARY}
|
|
892
|
+
size={EComponentSize.MD}
|
|
893
|
+
onClick={clearFilters}
|
|
894
|
+
>
|
|
895
|
+
Сбросить
|
|
896
|
+
</Button>
|
|
897
|
+
|
|
898
|
+
<Button
|
|
899
|
+
theme={EButtonTheme.GENERAL}
|
|
900
|
+
size={EComponentSize.MD}
|
|
901
|
+
onClick={() => setOpenLightBoxOptions(false)}
|
|
902
|
+
>
|
|
903
|
+
Применить
|
|
904
|
+
</Button>
|
|
905
|
+
</Page.Footer.Description.Controls>
|
|
906
|
+
</Page.Footer.Description>
|
|
907
|
+
</Page.Footer>
|
|
908
|
+
</Page>
|
|
909
|
+
</LightBox.Content>
|
|
910
|
+
|
|
911
|
+
<LightBox.Controls>
|
|
912
|
+
<LightBox.Controls.Close
|
|
913
|
+
title="Закрыть"
|
|
914
|
+
data-test-id="lightbox-close"
|
|
915
|
+
onClick={() => setOpenLightBoxOptions(false)}
|
|
916
|
+
/>
|
|
917
|
+
</LightBox.Controls>
|
|
918
|
+
</LightBox>
|
|
919
|
+
) : null}
|
|
920
|
+
</div>
|
|
921
|
+
);
|
|
922
|
+
};
|
|
923
|
+
```
|
|
201
924
|
|
|
202
|
-
| Дата | Изменение |
|
|
203
|
-
|---|---|
|
|
204
|
-
| 2026-04-18 | Создан документ |
|