@smartex.com/modules-utils 0.1.0 → 0.1.1
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 +302 -0
- package/package.json +4 -2
package/README.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# @smartex/modules-utils
|
|
2
|
+
|
|
3
|
+
Утилиты для создания, монтирования и управления жизненным циклом ES-модулей в рамках основного SPA-приложения.
|
|
4
|
+
|
|
5
|
+
Библиотека предоставляет два независимых набора инструментов:
|
|
6
|
+
|
|
7
|
+
- **base** — для виджет-модулей общего назначения (карточки, формы, информационные блоки).
|
|
8
|
+
- **survey** — для модулей опросов с полным управлением контекстом, историей навигации и ответами.
|
|
9
|
+
|
|
10
|
+
## Установка
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @smartex/modules-utils
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Peer-зависимости
|
|
17
|
+
|
|
18
|
+
Библиотека ожидает, что в проекте установлены:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react react-dom dayjs @smartex.com/ctx
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Виджет-модули (`base`)
|
|
25
|
+
|
|
26
|
+
### Использование
|
|
27
|
+
|
|
28
|
+
Виджет-модуль — это React-компонент, который принимает `ModuleProps<E>`, где `E` — тип внешних данных, специфичных для конкретного модуля.
|
|
29
|
+
|
|
30
|
+
Определите компонент:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// participant-info.tsx
|
|
34
|
+
import type { ModuleProps } from '@smartex/modules-utils/base';
|
|
35
|
+
|
|
36
|
+
interface ExternalData {
|
|
37
|
+
participantId: string;
|
|
38
|
+
callHash: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ParticipantInfo = ({ token, externalData }: ModuleProps<ExternalData>) => {
|
|
42
|
+
return <div>{externalData?.participantId}</div>;
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Экспортируйте модуль через контроллер:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// index.tsx
|
|
50
|
+
import { createModuleController } from '@smartex/modules-utils/base';
|
|
51
|
+
import { ParticipantInfo } from './participant-info';
|
|
52
|
+
|
|
53
|
+
const moduleController = createModuleController(ParticipantInfo);
|
|
54
|
+
|
|
55
|
+
export default moduleController;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Основное приложение монтирует модуль в DOM:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const mod = await loadModule('participant-info');
|
|
62
|
+
mod.mount('target-element-id', {
|
|
63
|
+
token: '...',
|
|
64
|
+
apiConfig: { services: '...', externalApi: '...' },
|
|
65
|
+
externalData: { participantId: '123', callHash: 'abc' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// При необходимости:
|
|
69
|
+
mod.unmount();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### API
|
|
73
|
+
|
|
74
|
+
#### `createModuleController<E>(Module)`
|
|
75
|
+
|
|
76
|
+
Создаёт контроллер с методами `mount` и `unmount`. Тип `E` (внешние данные) выводится автоматически из типа пропсов компонента.
|
|
77
|
+
|
|
78
|
+
#### `ModuleProps<T>`
|
|
79
|
+
|
|
80
|
+
| Поле | Тип | Обязательное | Описание |
|
|
81
|
+
| -------------- | ------------------------------------------- | ------------ | -------------------------------- |
|
|
82
|
+
| `token` | `string` | да | Токен авторизации |
|
|
83
|
+
| `lang` | `'ru' \| 'kz' \| 'en'` | нет | Язык интерфейса |
|
|
84
|
+
| `theme` | `string` | нет | Тема оформления |
|
|
85
|
+
| `externalData` | `T` | нет | Внешние данные для модуля |
|
|
86
|
+
| `apiConfig` | `{ services: string; externalApi: string }` | да | Конфигурация API |
|
|
87
|
+
| `onSuccess` | `() => void` | нет | Callback при успешном завершении |
|
|
88
|
+
| `onError` | `(errors: string[]) => void` | нет | Callback при ошибке |
|
|
89
|
+
|
|
90
|
+
## Модули опросов (`survey`)
|
|
91
|
+
|
|
92
|
+
Модули опросов используют фабричный паттерн. Библиотека предоставляет headless-обёртку (логика без UI), а проект определяет компонент-layout (отображение).
|
|
93
|
+
|
|
94
|
+
### Настройка в проекте
|
|
95
|
+
|
|
96
|
+
#### 1. Определите схему контекста
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// schema.ts
|
|
100
|
+
export const surveyCtxSchema = {
|
|
101
|
+
token: '',
|
|
102
|
+
user: { code: 0, name: '', role: { code: 0, name: '' }, companyCode: 0 },
|
|
103
|
+
session: { code: 0, id: '' },
|
|
104
|
+
lang: '',
|
|
105
|
+
theme: '',
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### 2. Объявите глобальный тип `ctx`
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// ctx.d.ts
|
|
113
|
+
import type { SurveyCtxStore } from '@smartex/modules-utils/survey';
|
|
114
|
+
import type { surveyCtxSchema } from './schema';
|
|
115
|
+
|
|
116
|
+
declare global {
|
|
117
|
+
interface Window {
|
|
118
|
+
ctx: SurveyCtxStore<typeof surveyCtxSchema>;
|
|
119
|
+
}
|
|
120
|
+
var ctx: SurveyCtxStore<typeof surveyCtxSchema>;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### 3. Определите тип внешних данных
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
// types.ts
|
|
128
|
+
export interface SurveyExternalData {
|
|
129
|
+
user: {
|
|
130
|
+
code: number;
|
|
131
|
+
name: string;
|
|
132
|
+
role: { code: number; name: string };
|
|
133
|
+
companyCode: number;
|
|
134
|
+
};
|
|
135
|
+
session: { code: number; id: string };
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### 4. Создайте layout-компонент
|
|
140
|
+
|
|
141
|
+
Layout получает `mountProps` (данные вопроса, язык), `surveyProps` (обработчики: `onSetAnswer`, `goPrev` и т.д.) и `children` (сам модуль):
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
// survey-layout.tsx
|
|
145
|
+
import type { ReactNode } from 'react';
|
|
146
|
+
import type { SurveyMountProps, SurveyComponentProps } from '@smartex/modules-utils/survey';
|
|
147
|
+
|
|
148
|
+
interface SurveyLayoutProps {
|
|
149
|
+
mountProps: SurveyMountProps;
|
|
150
|
+
surveyProps: SurveyComponentProps;
|
|
151
|
+
children: ReactNode;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const SurveyLayout = ({ mountProps, surveyProps, children }: SurveyLayoutProps) => {
|
|
155
|
+
const lang = mountProps.lang ?? 'ru';
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div>
|
|
159
|
+
<h1>{mountProps.question.text[lang]}</h1>
|
|
160
|
+
<div>{children}</div>
|
|
161
|
+
{!mountProps.question.isPrevStepDisabled && (
|
|
162
|
+
<button onClick={surveyProps.goPrev}>Назад</button>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### 5. Сконфигурируйте фабрику контроллера
|
|
170
|
+
|
|
171
|
+
Выполняется один раз. Все модули опросов импортируют результат:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// survey-module-controller.ts
|
|
175
|
+
import { createSurveyModuleControllerFactory } from '@smartex/modules-utils/survey';
|
|
176
|
+
import { SurveyLayout } from './survey-layout';
|
|
177
|
+
import { surveyCtxSchema } from './schema';
|
|
178
|
+
import type { SurveyExternalData } from './types';
|
|
179
|
+
|
|
180
|
+
export const createSurveyModuleController =
|
|
181
|
+
createSurveyModuleControllerFactory<SurveyExternalData>(SurveyLayout, {
|
|
182
|
+
schema: surveyCtxSchema,
|
|
183
|
+
defaultLang: 'ru',
|
|
184
|
+
logicScreenTypes: ['DYNAMIC_ROUTING'],
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Создание модуля опроса
|
|
189
|
+
|
|
190
|
+
Модуль получает `SurveyComponentProps` — набор пропсов, подготовленных headless-обёрткой:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// list-selection.tsx
|
|
194
|
+
import type { SurveyComponentProps } from '@smartex/modules-utils/survey';
|
|
195
|
+
|
|
196
|
+
export const ListSelection = ({ question, onSetAnswer, goNext }: SurveyComponentProps) => {
|
|
197
|
+
const handleSelect = (answerCode: number, nextQuestion: number) => {
|
|
198
|
+
onSetAnswer(answerCode, null);
|
|
199
|
+
goNext(nextQuestion);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<ul>
|
|
204
|
+
{question.answers.map((answer) => (
|
|
205
|
+
<li key={answer.code} onClick={() => handleSelect(answer.code, answer.nextQuestion!)}>
|
|
206
|
+
{answer.text.ru}
|
|
207
|
+
</li>
|
|
208
|
+
))}
|
|
209
|
+
</ul>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Экспорт через контроллер:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
// index.tsx
|
|
218
|
+
import { createSurveyModuleController } from '@/modules/_lib/survey/survey-module-controller';
|
|
219
|
+
import { ListSelection } from './list-selection';
|
|
220
|
+
|
|
221
|
+
const moduleController = createSurveyModuleController(ListSelection);
|
|
222
|
+
|
|
223
|
+
export default moduleController;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### API
|
|
227
|
+
|
|
228
|
+
#### `createSurveyModuleControllerFactory<E>(Layout, config)`
|
|
229
|
+
|
|
230
|
+
Фабрика, возвращающая функцию `createSurveyModuleController`.
|
|
231
|
+
|
|
232
|
+
| Параметр | Тип | Описание |
|
|
233
|
+
| -------- | -------------------------- | ------------------------------------------------ |
|
|
234
|
+
| `Layout` | `SurveyLayoutComponent` | React-компонент для отображения обёртки модуля |
|
|
235
|
+
| `config` | `SurveyModuleWrapperConfig`| Конфигурация (см. ниже) |
|
|
236
|
+
|
|
237
|
+
#### `SurveyModuleWrapperConfig`
|
|
238
|
+
|
|
239
|
+
| Поле | Тип | Описание |
|
|
240
|
+
| ------------------ | ------------------------ | ------------------------------------------------------------ |
|
|
241
|
+
| `schema` | `Record<string, unknown>` | Схема контекста для `@smartex.com/ctx` |
|
|
242
|
+
| `defaultLang` | `'ru' \| 'kz' \| 'en'` | Язык по умолчанию |
|
|
243
|
+
| `logicScreenTypes` | `string[]` | Типы вопросов-экранов, пропускаемых при навигации назад |
|
|
244
|
+
|
|
245
|
+
#### `SurveyComponentProps`
|
|
246
|
+
|
|
247
|
+
Пропсы, которые получает каждый модуль опроса:
|
|
248
|
+
|
|
249
|
+
| Поле | Тип | Описание |
|
|
250
|
+
| ---------------- | ---------------------------------------------------- | ------------------------------------------- |
|
|
251
|
+
| `question` | `SurveyQuestion` | Данные текущего вопроса |
|
|
252
|
+
| `getSettings` | `<T>(key?: string) => [T \| null, string \| null]` | Получение настроек вопроса |
|
|
253
|
+
| `onSetAnswer` | `(answer: number \| number[], value: unknown) => void`| Сохранение ответа |
|
|
254
|
+
| `goNext` | `(questionCode: number) => void` | Переход к следующему вопросу |
|
|
255
|
+
| `goPrev` | `() => void` | Переход к предыдущему вопросу |
|
|
256
|
+
| `onSuccess` | `() => void` | Сигнал успешного завершения |
|
|
257
|
+
| `onError` | `(errors: string[]) => void` | Сигнал ошибки |
|
|
258
|
+
| `onSurveyFinish` | `() => void` | Завершение опроса |
|
|
259
|
+
|
|
260
|
+
#### `SurveyLayoutComponent`
|
|
261
|
+
|
|
262
|
+
Тип layout-компонента, ожидаемый фабрикой:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
type SurveyLayoutComponent = ComponentType<{
|
|
266
|
+
mountProps: SurveyMountProps;
|
|
267
|
+
surveyProps: SurveyComponentProps;
|
|
268
|
+
children: ReactNode;
|
|
269
|
+
}>;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Инициализация контекста
|
|
273
|
+
|
|
274
|
+
Headless-обёртка автоматически управляет жизненным циклом `ctx`:
|
|
275
|
+
|
|
276
|
+
- Создаёт контекст на первом вопросе опроса.
|
|
277
|
+
- Записывает `token`, `lang`, `theme` из пропсов монтирования.
|
|
278
|
+
- Итерирует `externalData` и записывает каждый ключ через `ctx.set()`. Ключи `externalData` должны совпадать с ключами схемы — `ctx` валидирует их в рантайме.
|
|
279
|
+
- Уничтожает контекст при навигации назад с первого вопроса.
|
|
280
|
+
|
|
281
|
+
Проект не вызывает методы `ctx` напрямую при настройке — только модули используют глобальный `ctx` в своей логике.
|
|
282
|
+
|
|
283
|
+
## Архитектура
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
Основное приложение
|
|
287
|
+
└── module.mount(id, props) // знает только пропсы монтирования
|
|
288
|
+
│
|
|
289
|
+
▼
|
|
290
|
+
Контроллер модуля // mount / unmount
|
|
291
|
+
└── createRoot + render
|
|
292
|
+
│
|
|
293
|
+
├── SurveyModuleWrapper // библиотека: ctx, история, ответы (без UI)
|
|
294
|
+
│ └── children(injectedProps)
|
|
295
|
+
│ │
|
|
296
|
+
│ ▼
|
|
297
|
+
├── SurveyLayout // проект: заголовок, кнопки, стили
|
|
298
|
+
│ └── children
|
|
299
|
+
│ │
|
|
300
|
+
│ ▼
|
|
301
|
+
└── Module // компонент модуля (SurveyComponentProps)
|
|
302
|
+
```
|