@smartex.com/modules-utils 0.1.0
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/dist/base/index.d.ts +28 -0
- package/dist/base/index.js +36 -0
- package/dist/base/index.js.map +1 -0
- package/dist/survey/index.d.ts +98 -0
- package/dist/survey/index.js +167 -0
- package/dist/survey/index.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface ModuleProps<T extends object = object> {
|
|
5
|
+
token: string;
|
|
6
|
+
lang?: 'ru' | 'kz' | 'en';
|
|
7
|
+
theme?: string;
|
|
8
|
+
externalData?: T;
|
|
9
|
+
apiConfig: {
|
|
10
|
+
services: string;
|
|
11
|
+
externalApi: string;
|
|
12
|
+
};
|
|
13
|
+
onSuccess?: () => void;
|
|
14
|
+
onError?: (errors: string[]) => void;
|
|
15
|
+
}
|
|
16
|
+
interface ModuleController<T extends object = object> {
|
|
17
|
+
mount: (targetNodeId: string, props: ModuleProps<T>) => void;
|
|
18
|
+
unmount: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare const createModuleController: <E extends object = object>(Module: ComponentType<ModuleProps<E>>) => ModuleController<E>;
|
|
22
|
+
|
|
23
|
+
type WrapperProps = ModuleProps & {
|
|
24
|
+
children: (injectedProps: ModuleProps) => ReactNode;
|
|
25
|
+
};
|
|
26
|
+
declare const ModuleWrapper: ({ children, ...props }: WrapperProps) => react.JSX.Element;
|
|
27
|
+
|
|
28
|
+
export { type ModuleController, type ModuleProps, ModuleWrapper, createModuleController };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/base/module-controller.tsx
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
// src/base/module-wrapper.tsx
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var ModuleWrapper = ({ children, ...props }) => {
|
|
7
|
+
return /* @__PURE__ */ jsx("div", { children: children(props) });
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/base/module-controller.tsx
|
|
11
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
12
|
+
var createModuleController = (Module) => {
|
|
13
|
+
let root = null;
|
|
14
|
+
const mount = (targetNodeId, props) => {
|
|
15
|
+
const el = document.getElementById(targetNodeId);
|
|
16
|
+
if (!el) {
|
|
17
|
+
throw new Error(`DOM \u044D\u043B\u0435\u043C\u0435\u043D\u0442 "${targetNodeId}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`);
|
|
18
|
+
}
|
|
19
|
+
root = createRoot(el);
|
|
20
|
+
root.render(
|
|
21
|
+
/* @__PURE__ */ jsx2(ModuleWrapper, { ...props, children: (injectedProps) => /* @__PURE__ */ jsx2(Module, { ...injectedProps }) })
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
const unmount = () => {
|
|
25
|
+
if (root !== null) {
|
|
26
|
+
root.unmount();
|
|
27
|
+
root = null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return { mount, unmount };
|
|
31
|
+
};
|
|
32
|
+
export {
|
|
33
|
+
ModuleWrapper,
|
|
34
|
+
createModuleController
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/base/module-controller.tsx","../../src/base/module-wrapper.tsx"],"sourcesContent":["import type { ComponentType } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { ModuleWrapper } from './module-wrapper';\nimport type { ModuleProps, ModuleController } from './types.ts';\n\nexport const createModuleController = <E extends object = object>(\n Module: ComponentType<ModuleProps<E>>\n): ModuleController<E> => {\n let root: ReturnType<typeof createRoot> | null = null;\n\n const mount: ModuleController['mount'] = (targetNodeId, props) => {\n const el = document.getElementById(targetNodeId);\n\n if (!el) {\n throw new Error(`DOM элемент \"${targetNodeId}\" не найден.`);\n }\n\n root = createRoot(el);\n root.render(\n <ModuleWrapper {...props}>\n {(injectedProps: ModuleProps) => <Module {...(injectedProps as ModuleProps<E>)} />}\n </ModuleWrapper>\n );\n };\n\n const unmount: ModuleController['unmount'] = () => {\n if (root !== null) {\n root.unmount();\n root = null;\n }\n };\n\n return { mount, unmount };\n};\n","import { type ReactNode } from 'react';\nimport type { ModuleProps } from './types.ts';\n\ntype WrapperProps = ModuleProps & {\n children: (injectedProps: ModuleProps) => ReactNode;\n};\n\nexport const ModuleWrapper = ({ children, ...props }: WrapperProps) => {\n return <div>{children(props)}</div>;\n};\n"],"mappings":";AACA,SAAS,kBAAkB;;;ACOlB;AADF,IAAM,gBAAgB,CAAC,EAAE,UAAU,GAAG,MAAM,MAAoB;AACrE,SAAO,oBAAC,SAAK,mBAAS,KAAK,GAAE;AAC/B;;;ADWyC,gBAAAA,YAAA;AAflC,IAAM,yBAAyB,CACpC,WACwB;AACxB,MAAI,OAA6C;AAEjD,QAAM,QAAmC,CAAC,cAAc,UAAU;AAChE,UAAM,KAAK,SAAS,eAAe,YAAY;AAE/C,QAAI,CAAC,IAAI;AACP,YAAM,IAAI,MAAM,mDAAgB,YAAY,sDAAc;AAAA,IAC5D;AAEA,WAAO,WAAW,EAAE;AACpB,SAAK;AAAA,MACH,gBAAAA,KAAC,iBAAe,GAAG,OAChB,WAAC,kBAA+B,gBAAAA,KAAC,UAAQ,GAAI,eAAkC,GAClF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAuC,MAAM;AACjD,QAAI,SAAS,MAAM;AACjB,WAAK,QAAQ;AACb,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;","names":["jsx"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { SurveyResultEntry, CtxStoreWithSurvey } from '@smartex.com/ctx';
|
|
3
|
+
|
|
4
|
+
type Lang = 'ru' | 'kz' | 'en';
|
|
5
|
+
interface SurveyAnswer {
|
|
6
|
+
code: number;
|
|
7
|
+
text: {
|
|
8
|
+
ru: string | null;
|
|
9
|
+
kz: string | null;
|
|
10
|
+
en: string | null;
|
|
11
|
+
};
|
|
12
|
+
photo: {
|
|
13
|
+
ru: string | null;
|
|
14
|
+
kz: string | null;
|
|
15
|
+
en: string | null;
|
|
16
|
+
};
|
|
17
|
+
nextQuestion: number | null;
|
|
18
|
+
minAllowedValue: number | null;
|
|
19
|
+
maxAllowedValue: number | null;
|
|
20
|
+
htmlContent: string | null;
|
|
21
|
+
ctxSettings: string | null;
|
|
22
|
+
weight: number;
|
|
23
|
+
order: number;
|
|
24
|
+
isMandatory: number;
|
|
25
|
+
isCustomRouting: boolean;
|
|
26
|
+
isCustomInput: boolean;
|
|
27
|
+
}
|
|
28
|
+
interface SurveyQuestion {
|
|
29
|
+
code: number;
|
|
30
|
+
text: {
|
|
31
|
+
ru: string | null;
|
|
32
|
+
kz: string | null;
|
|
33
|
+
en: string | null;
|
|
34
|
+
};
|
|
35
|
+
photo: {
|
|
36
|
+
ru: string | null;
|
|
37
|
+
kz: string | null;
|
|
38
|
+
en: string | null;
|
|
39
|
+
};
|
|
40
|
+
typeCode: string;
|
|
41
|
+
typeName: string;
|
|
42
|
+
settings: string | null;
|
|
43
|
+
answers: SurveyAnswer[];
|
|
44
|
+
htmlContent: string | null;
|
|
45
|
+
iframeContent: string | null;
|
|
46
|
+
theme: string;
|
|
47
|
+
modulePath: string;
|
|
48
|
+
isFirst: boolean;
|
|
49
|
+
isPrevStepDisabled: boolean;
|
|
50
|
+
isModule: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface SurveyMountProps<T extends object = object> {
|
|
53
|
+
token: string;
|
|
54
|
+
lang?: Lang;
|
|
55
|
+
theme?: string;
|
|
56
|
+
externalData?: T;
|
|
57
|
+
question: SurveyQuestion;
|
|
58
|
+
surveyCode: number;
|
|
59
|
+
fusCode?: number;
|
|
60
|
+
visit?: number;
|
|
61
|
+
parentVisit?: number;
|
|
62
|
+
apiConfig: {
|
|
63
|
+
services: string;
|
|
64
|
+
externalApi: string;
|
|
65
|
+
};
|
|
66
|
+
routeToQuestion: (questionCode: number) => void;
|
|
67
|
+
onSurveyFinish: () => void;
|
|
68
|
+
onError: (errors: string[]) => void;
|
|
69
|
+
}
|
|
70
|
+
type SurveyComponentProps = {
|
|
71
|
+
question: SurveyMountProps['question'];
|
|
72
|
+
getSettings: <T = unknown>(key?: string) => [T | null, string | null];
|
|
73
|
+
onSetAnswer: (answer: number | number[], value: SurveyResultEntry['result']) => void;
|
|
74
|
+
goNext: SurveyMountProps['routeToQuestion'];
|
|
75
|
+
goPrev: () => void;
|
|
76
|
+
onSuccess: () => void;
|
|
77
|
+
onError: (errors: string[]) => void;
|
|
78
|
+
onSurveyFinish: SurveyMountProps['onSurveyFinish'];
|
|
79
|
+
};
|
|
80
|
+
interface SurveyModuleController<E extends object = object> {
|
|
81
|
+
mount: (targetNodeId: string, props: SurveyMountProps<E>) => void;
|
|
82
|
+
unmount: () => void;
|
|
83
|
+
}
|
|
84
|
+
interface SurveyModuleWrapperConfig {
|
|
85
|
+
schema: Record<string, unknown>;
|
|
86
|
+
defaultLang: Lang;
|
|
87
|
+
logicScreenAliases: string[];
|
|
88
|
+
}
|
|
89
|
+
type SurveyCtxStore<S extends Record<string, unknown>> = CtxStoreWithSurvey<S>;
|
|
90
|
+
|
|
91
|
+
type SurveyLayout = ComponentType<{
|
|
92
|
+
mountProps: SurveyMountProps;
|
|
93
|
+
surveyProps: SurveyComponentProps;
|
|
94
|
+
children: ReactNode;
|
|
95
|
+
}>;
|
|
96
|
+
declare const createSurveyModuleControllerFactory: <E extends object = object>(Layout: SurveyLayout, config: SurveyModuleWrapperConfig) => (Module: ComponentType<SurveyComponentProps>) => SurveyModuleController<E>;
|
|
97
|
+
|
|
98
|
+
export { type SurveyAnswer, type SurveyComponentProps, type SurveyCtxStore, type SurveyModuleController, type SurveyModuleWrapperConfig, type SurveyMountProps, type SurveyQuestion, createSurveyModuleControllerFactory };
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// src/survey/survey-module-controller-factory.tsx
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
|
|
4
|
+
// src/survey/survey-module-wrapper.tsx
|
|
5
|
+
import { useState, useMemo, useLayoutEffect, useCallback } from "react";
|
|
6
|
+
import dayjs from "dayjs";
|
|
7
|
+
import { create } from "@smartex.com/ctx";
|
|
8
|
+
var getCtx = () => {
|
|
9
|
+
const store = window.ctx;
|
|
10
|
+
if (!store) {
|
|
11
|
+
throw new Error("ctx is not initialized.");
|
|
12
|
+
}
|
|
13
|
+
return store;
|
|
14
|
+
};
|
|
15
|
+
var hasCtx = () => !!window.ctx;
|
|
16
|
+
var SurveyModuleWrapper = ({ children, config, ...props }) => {
|
|
17
|
+
const [ctxReady, setCtxReady] = useState(false);
|
|
18
|
+
const startTime = useMemo(() => dayjs().format("DD.MM.YYYY HH:mm:ss"), []);
|
|
19
|
+
const initSurveyCtx = () => {
|
|
20
|
+
create({
|
|
21
|
+
name: "ctx",
|
|
22
|
+
schema: config.schema,
|
|
23
|
+
survey: { code: props.surveyCode }
|
|
24
|
+
});
|
|
25
|
+
const ctx = getCtx();
|
|
26
|
+
ctx.set("token", props.token);
|
|
27
|
+
ctx.set("lang", props.lang ?? config.defaultLang);
|
|
28
|
+
ctx.set("theme", props.theme ?? null);
|
|
29
|
+
if (props.externalData) {
|
|
30
|
+
for (const [key, value] of Object.entries(props.externalData)) {
|
|
31
|
+
ctx.set(key, value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const getPrevQuestionCode = () => {
|
|
36
|
+
const history = getCtx().survey.getHistory();
|
|
37
|
+
return history.length > 0 ? history[history.length - 1].code : null;
|
|
38
|
+
};
|
|
39
|
+
const getPrevQuestionData = () => {
|
|
40
|
+
const prevQuestion = getPrevQuestionCode();
|
|
41
|
+
return prevQuestion ? getCtx().survey.getData(prevQuestion) : {};
|
|
42
|
+
};
|
|
43
|
+
const getSettings = useCallback((key) => {
|
|
44
|
+
if (!props.question.settings) {
|
|
45
|
+
return [null, null];
|
|
46
|
+
}
|
|
47
|
+
let settings;
|
|
48
|
+
try {
|
|
49
|
+
settings = JSON.parse(props.question.settings);
|
|
50
|
+
} catch {
|
|
51
|
+
return [null, "\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0430\u0440\u0441\u0438\u043D\u0433\u0430 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A \u0432\u043E\u043F\u0440\u043E\u0441\u0430"];
|
|
52
|
+
}
|
|
53
|
+
if (Object.keys(settings).length === 0) return [null, null];
|
|
54
|
+
if (!key) return [settings, null];
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(settings, key)) {
|
|
56
|
+
return [null, `\u0412 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u043D\u0435\u0442 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430 "${key}"`];
|
|
57
|
+
}
|
|
58
|
+
return [settings[key], null];
|
|
59
|
+
}, []);
|
|
60
|
+
const routeToPrevQuestion = (questionCode) => {
|
|
61
|
+
const ctx = getCtx();
|
|
62
|
+
const currentQuestionCode = questionCode ?? props.question.code;
|
|
63
|
+
const prevQuestionCode = getPrevQuestionCode();
|
|
64
|
+
const prevQuestionType = prevQuestionCode ? ctx.survey.getResult(prevQuestionCode)?.questionType : null;
|
|
65
|
+
if (prevQuestionCode === null) {
|
|
66
|
+
ctx.destroy();
|
|
67
|
+
props.onSurveyFinish();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
ctx.survey.removeData(currentQuestionCode);
|
|
71
|
+
ctx.survey.removeResult(currentQuestionCode);
|
|
72
|
+
ctx.survey.undoHistory();
|
|
73
|
+
if (config.logicScreenAliases.includes(prevQuestionType)) {
|
|
74
|
+
routeToPrevQuestion(prevQuestionCode);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
props.routeToQuestion(prevQuestionCode);
|
|
78
|
+
};
|
|
79
|
+
const goPrev = useCallback(() => routeToPrevQuestion(), []);
|
|
80
|
+
const onSetAnswer = useCallback(
|
|
81
|
+
(answer, value) => {
|
|
82
|
+
const ctx = getCtx();
|
|
83
|
+
let weight = 0;
|
|
84
|
+
if (Array.isArray(answer)) {
|
|
85
|
+
weight = props.question.answers.filter((item) => answer.includes(item.code)).reduce((sum, item) => sum + (item.weight ?? 0), 0);
|
|
86
|
+
} else {
|
|
87
|
+
weight = props.question.answers.find((item) => item.code === answer)?.weight ?? 0;
|
|
88
|
+
}
|
|
89
|
+
ctx.survey.setAnswerWeight(props.question.code, weight);
|
|
90
|
+
ctx.survey.addHistory({
|
|
91
|
+
code: props.question.code,
|
|
92
|
+
answers: Array.isArray(answer) ? [...answer] : [answer],
|
|
93
|
+
stime: startTime,
|
|
94
|
+
etime: dayjs().format("DD.MM.YYYY HH:mm:ss")
|
|
95
|
+
});
|
|
96
|
+
ctx.survey.addResult({
|
|
97
|
+
questionCode: props.question.code,
|
|
98
|
+
questionType: props.question.typeCode,
|
|
99
|
+
answerCode: Array.isArray(answer) ? null : answer,
|
|
100
|
+
result: value
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
[]
|
|
104
|
+
);
|
|
105
|
+
const onSuccess = useCallback(() => {
|
|
106
|
+
const baseAnswers = props.question.answers.filter((answer) => !answer.isCustomRouting);
|
|
107
|
+
onSetAnswer(baseAnswers[0].code, null);
|
|
108
|
+
props.routeToQuestion(baseAnswers[0].nextQuestion);
|
|
109
|
+
}, []);
|
|
110
|
+
const onError = useCallback((errors) => {
|
|
111
|
+
props.onError(errors);
|
|
112
|
+
}, []);
|
|
113
|
+
useLayoutEffect(() => {
|
|
114
|
+
if (props.question.isFirst) {
|
|
115
|
+
if (hasCtx()) getCtx().destroy();
|
|
116
|
+
initSurveyCtx();
|
|
117
|
+
}
|
|
118
|
+
if (!hasCtx()) {
|
|
119
|
+
throw new Error("\u041E\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0434\u043B\u044F \u0440\u0430\u0431\u043E\u0442\u044B \u0441 \u043C\u043E\u0434\u0443\u043B\u044F\u043C\u0438!");
|
|
120
|
+
}
|
|
121
|
+
const ctx = getCtx();
|
|
122
|
+
ctx.survey.removeData(props.question.code);
|
|
123
|
+
ctx.survey.removeResult(props.question.code);
|
|
124
|
+
ctx.survey.addData(props.question.code, getPrevQuestionData(), true);
|
|
125
|
+
setCtxReady(true);
|
|
126
|
+
}, []);
|
|
127
|
+
if (!ctxReady) return null;
|
|
128
|
+
return children({
|
|
129
|
+
getSettings,
|
|
130
|
+
onSetAnswer,
|
|
131
|
+
onSuccess,
|
|
132
|
+
onError,
|
|
133
|
+
goPrev,
|
|
134
|
+
goNext: props.routeToQuestion,
|
|
135
|
+
onSurveyFinish: props.onSurveyFinish,
|
|
136
|
+
question: props.question
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/survey/survey-module-controller-factory.tsx
|
|
141
|
+
import { jsx } from "react/jsx-runtime";
|
|
142
|
+
var createSurveyModuleControllerFactory = (Layout, config) => {
|
|
143
|
+
return (Module) => {
|
|
144
|
+
let root = null;
|
|
145
|
+
const mount = (targetNodeId, props) => {
|
|
146
|
+
const el = document.getElementById(targetNodeId);
|
|
147
|
+
if (!el) {
|
|
148
|
+
throw new Error(`DOM \u044D\u043B\u0435\u043C\u0435\u043D\u0442 "${targetNodeId}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D.`);
|
|
149
|
+
}
|
|
150
|
+
root = createRoot(el);
|
|
151
|
+
root.render(
|
|
152
|
+
/* @__PURE__ */ jsx(SurveyModuleWrapper, { ...props, config, children: (injectedProps) => /* @__PURE__ */ jsx(Layout, { mountProps: props, surveyProps: injectedProps, children: /* @__PURE__ */ jsx(Module, { ...injectedProps }) }) })
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
const unmount = () => {
|
|
156
|
+
if (root !== null) {
|
|
157
|
+
root.unmount();
|
|
158
|
+
root = null;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
return { mount, unmount };
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
export {
|
|
165
|
+
createSurveyModuleControllerFactory
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/survey/survey-module-controller-factory.tsx","../../src/survey/survey-module-wrapper.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { SurveyModuleWrapper } from './survey-module-wrapper';\nimport type {\n SurveyMountProps,\n SurveyComponentProps,\n SurveyModuleController,\n SurveyModuleWrapperConfig,\n} from './types.ts';\n\ntype SurveyLayout = ComponentType<{\n mountProps: SurveyMountProps;\n surveyProps: SurveyComponentProps;\n children: ReactNode;\n}>;\n\nexport const createSurveyModuleControllerFactory = <E extends object = object>(\n Layout: SurveyLayout,\n config: SurveyModuleWrapperConfig\n) => {\n return (Module: ComponentType<SurveyComponentProps>): SurveyModuleController<E> => {\n let root: ReturnType<typeof createRoot> | null = null;\n\n const mount = (targetNodeId: string, props: SurveyMountProps<E>) => {\n const el = document.getElementById(targetNodeId);\n\n if (!el) {\n throw new Error(`DOM элемент \"${targetNodeId}\" не найден.`);\n }\n\n root = createRoot(el);\n root.render(\n <SurveyModuleWrapper {...(props as SurveyMountProps)} config={config}>\n {(injectedProps: SurveyComponentProps) => (\n <Layout mountProps={props as SurveyMountProps} surveyProps={injectedProps}>\n <Module {...injectedProps} />\n </Layout>\n )}\n </SurveyModuleWrapper>\n );\n };\n\n const unmount = () => {\n if (root !== null) {\n root.unmount();\n root = null;\n }\n };\n\n return { mount, unmount };\n };\n};\n","import { useState, useMemo, useLayoutEffect, useCallback, type ReactNode } from 'react';\nimport dayjs from 'dayjs';\nimport { create, type CtxStoreWithSurvey, type SurveyResultEntry } from '@smartex.com/ctx';\nimport type { SurveyMountProps, SurveyComponentProps, SurveyModuleWrapperConfig } from './types.ts';\n\ntype AnyCtxStore = CtxStoreWithSurvey<Record<string, unknown>>;\n\n/**\n * Internal accessor for the global ctx store.\n * Avoids declaring `ctx` on `Window` in the library — that declaration\n * belongs to the consuming project, which knows the actual schema type.\n */\nconst getCtx = (): AnyCtxStore => {\n const store = (window as any).ctx as AnyCtxStore | undefined;\n\n if (!store) {\n throw new Error('ctx is not initialized.');\n }\n\n return store;\n};\n\nconst hasCtx = (): boolean => !!(window as any).ctx;\n\ntype SurveyWrapperProps = SurveyMountProps & {\n config: SurveyModuleWrapperConfig;\n children: (injectedProps: SurveyComponentProps) => ReactNode;\n};\n\nexport const SurveyModuleWrapper = ({ children, config, ...props }: SurveyWrapperProps) => {\n const [ctxReady, setCtxReady] = useState(false);\n const startTime = useMemo(() => dayjs().format('DD.MM.YYYY HH:mm:ss'), []);\n\n const initSurveyCtx = () => {\n create({\n name: 'ctx',\n schema: config.schema,\n survey: { code: props.surveyCode },\n });\n\n const ctx = getCtx();\n ctx.set('token', props.token);\n ctx.set('lang', props.lang ?? config.defaultLang);\n ctx.set('theme', props.theme ?? null);\n\n if (props.externalData) {\n for (const [key, value] of Object.entries(props.externalData)) {\n ctx.set(key, value);\n }\n }\n };\n\n const getPrevQuestionCode = () => {\n const history = getCtx().survey.getHistory();\n return history.length > 0 ? history[history.length - 1].code : null;\n };\n\n const getPrevQuestionData = () => {\n const prevQuestion = getPrevQuestionCode();\n return prevQuestion ? getCtx().survey.getData(prevQuestion) : {};\n };\n\n const getSettings = useCallback(<T = unknown,>(key?: string): [T | null, string | null] => {\n if (!props.question.settings) {\n return [null, null];\n }\n\n let settings: Record<string, unknown>;\n\n try {\n settings = JSON.parse(props.question.settings);\n } catch {\n return [null, 'Ошибка парсинга настроек вопроса'];\n }\n\n if (Object.keys(settings).length === 0) return [null, null];\n if (!key) return [settings as T, null];\n if (!Object.prototype.hasOwnProperty.call(settings, key)) {\n return [null, `В настройках вопроса нет параметра \"${key}\"`];\n }\n\n return [settings[key] as T, null];\n }, []);\n\n const routeToPrevQuestion = (questionCode?: number) => {\n const ctx = getCtx();\n const currentQuestionCode = questionCode ?? props.question.code;\n const prevQuestionCode = getPrevQuestionCode();\n const prevQuestionType = prevQuestionCode\n ? ctx.survey.getResult(prevQuestionCode)?.questionType\n : null;\n\n if (prevQuestionCode === null) {\n ctx.destroy();\n props.onSurveyFinish(); // TODO: РЕШИТЬ ЧТО ТУТ ДЕЛАТЬ !!!!!!!!!!!!!!!\n return;\n }\n\n ctx.survey.removeData(currentQuestionCode);\n ctx.survey.removeResult(currentQuestionCode);\n ctx.survey.undoHistory();\n\n if (config.logicScreenAliases.includes(prevQuestionType as string)) {\n routeToPrevQuestion(prevQuestionCode);\n return;\n }\n\n props.routeToQuestion(prevQuestionCode);\n };\n\n const goPrev = useCallback(() => routeToPrevQuestion(), []);\n\n const onSetAnswer = useCallback(\n (answer: number | number[], value: SurveyResultEntry['result']) => {\n const ctx = getCtx();\n let weight = 0;\n\n if (Array.isArray(answer)) {\n weight = props.question.answers\n .filter((item) => answer.includes(item.code))\n .reduce((sum, item) => sum + (item.weight ?? 0), 0);\n } else {\n weight = props.question.answers.find((item) => item.code === answer)?.weight ?? 0;\n }\n\n ctx.survey.setAnswerWeight(props.question.code, weight);\n ctx.survey.addHistory({\n code: props.question.code,\n answers: Array.isArray(answer) ? [...answer] : [answer],\n stime: startTime,\n etime: dayjs().format('DD.MM.YYYY HH:mm:ss'),\n });\n ctx.survey.addResult({\n questionCode: props.question.code,\n questionType: props.question.typeCode,\n answerCode: Array.isArray(answer) ? null : answer,\n result: value,\n });\n },\n []\n );\n\n const onSuccess = useCallback(() => {\n const baseAnswers = props.question.answers.filter((answer) => !answer.isCustomRouting);\n onSetAnswer(baseAnswers[0].code, null);\n props.routeToQuestion(baseAnswers[0].nextQuestion as number);\n }, []);\n\n const onError = useCallback((errors: string[]) => {\n // SEND LOGS via API\n props.onError(errors);\n }, []);\n\n useLayoutEffect(() => {\n if (props.question.isFirst) {\n if (hasCtx()) getCtx().destroy();\n initSurveyCtx();\n }\n\n if (!hasCtx()) {\n throw new Error('Отсутствует контекст для работы с модулями!');\n }\n\n const ctx = getCtx();\n // Results and data cleanup on module init\n ctx.survey.removeData(props.question.code);\n ctx.survey.removeResult(props.question.code);\n ctx.survey.addData(props.question.code, getPrevQuestionData(), true);\n setCtxReady(true); // eslint-disable-line react-hooks/set-state-in-effect\n }, []);\n\n if (!ctxReady) return null;\n\n return children({\n getSettings,\n onSetAnswer,\n onSuccess,\n onError,\n goPrev,\n goNext: props.routeToQuestion,\n onSurveyFinish: props.onSurveyFinish,\n question: props.question,\n });\n};\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smartex.com/modules-utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": ["dist"],
|
|
6
|
+
"exports": {
|
|
7
|
+
"./base": {
|
|
8
|
+
"types": "./dist/base/index.d.ts",
|
|
9
|
+
"import": "./dist/base/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./survey": {
|
|
12
|
+
"types": "./dist/survey/index.d.ts",
|
|
13
|
+
"import": "./dist/survey/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18",
|
|
22
|
+
"react-dom": ">=18",
|
|
23
|
+
"dayjs": ">=1",
|
|
24
|
+
"@smartex.com/ctx": "*"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "^5.x",
|
|
28
|
+
"tsup": "^8.x",
|
|
29
|
+
"@types/react": "^18.x",
|
|
30
|
+
"@types/react-dom": "^18.x",
|
|
31
|
+
"dayjs": "^1.x",
|
|
32
|
+
"@smartex.com/ctx": "*"
|
|
33
|
+
}
|
|
34
|
+
}
|