module-package-comp 1.4.7 → 1.4.8
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/component/inputs/MultiSelect.js +1 -1
- package/dist/component/inputs/MultiSelect.js.map +1 -1
- package/package.json +1 -1
- package/src/component/ComponentDisplayer.tsx +0 -140
- package/src/component/PrimaryBtn.tsx +0 -22
- package/src/component/ShowAttr.tsx +0 -177
- package/src/component/ShowErrors.tsx +0 -20
- package/src/component/icons/WrongIcon.tsx +0 -30
- package/src/component/inputs/FileInput.tsx +0 -37
- package/src/component/inputs/Input.tsx +0 -54
- package/src/component/inputs/LabeledMutliSelect.tsx +0 -15
- package/src/component/inputs/LableledFileInput.tsx +0 -14
- package/src/component/inputs/LableledInput.tsx +0 -14
- package/src/component/inputs/LableledSelect.tsx +0 -14
- package/src/component/inputs/MultiSelect.tsx +0 -89
- package/src/component/inputs/Select.tsx +0 -33
- package/src/index.css +0 -3
- package/src/index.tsx +0 -6
- package/src/util/type.ts +0 -124
|
@@ -16,7 +16,7 @@ const MultiSelect = ({ options, selectedOptions, onChange, disabled, }) => {
|
|
|
16
16
|
setFilter(e.target.value);
|
|
17
17
|
};
|
|
18
18
|
const filteredOptions = options.filter((option) => option.toLowerCase().includes(filter.toLowerCase()));
|
|
19
|
-
return (_jsxs("div", { className: "relative inline-block w-full", children: [_jsx("div", { className: "py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none", onClick: toggleDropdown, children: Array.isArray(selectedOptions) && selectedOptions.length > 0 ? (selectedOptions.map((option) => (_jsx("span", { className: "mr-1 inline-block rounded-full bg-
|
|
19
|
+
return (_jsxs("div", { className: "relative inline-block w-full", children: [_jsx("div", { className: "py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none", onClick: toggleDropdown, children: Array.isArray(selectedOptions) && selectedOptions.length > 0 ? (selectedOptions.map((option) => (_jsx("span", { className: "mr-1 inline-block rounded-full bg-[#0096DC] px-2 py-1 text-sm", children: option }, option)))) : (_jsx("span", { className: "text-placeholder-gray", children: "Select options" })) }), isOpen && (_jsxs("div", { className: "absolute left-0 right-0 z-10 mt-2 rounded-md border border-secondary-gray bg-white", children: [_jsx("input", { type: "text", className: "w-full border-b border-secondary-gray p-2", placeholder: "Filter options...", value: filter, onChange: handleFilterChange }), _jsx("div", { className: "max-h-60 overflow-y-auto", children: filteredOptions.map((option) => (_jsxs("div", { className: "cursor-pointer p-2 hover:bg-primary-gray", onClick: () => {
|
|
20
20
|
if (!disabled)
|
|
21
21
|
handleOptionClick(option);
|
|
22
22
|
}, children: [_jsx("input", { type: "checkbox", checked: selectedOptions.includes(option), readOnly: true, className: "mr-2" }), _jsx("span", { className: "ml-2", children: option })] }, option))) })] }))] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultiSelect.js","sourceRoot":"","sources":["../../../src/component/inputs/MultiSelect.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AASxC,MAAM,WAAW,GAA+B,CAAC,EAC/C,OAAO,EACP,eAAe,EACf,QAAQ,EACR,QAAQ,GACT,EAAE,EAAE;IACH,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEzC,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAEhD,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAE,EAAE;QAC3C,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC;IACF,MAAM,kBAAkB,GAAG,CAAC,CAAsC,EAAE,EAAE;QACpE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAChD,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CACpD,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,cACE,SAAS,EAAC,wKAAwK,EAClL,OAAO,EAAE,cAAc,YAEtB,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAC9D,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC9B,eAEE,SAAS,EAAC,
|
|
1
|
+
{"version":3,"file":"MultiSelect.js","sourceRoot":"","sources":["../../../src/component/inputs/MultiSelect.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AASxC,MAAM,WAAW,GAA+B,CAAC,EAC/C,OAAO,EACP,eAAe,EACf,QAAQ,EACR,QAAQ,GACT,EAAE,EAAE;IACH,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEzC,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAEhD,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAE,EAAE;QAC3C,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,CAAC,GAAG,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC;IACF,MAAM,kBAAkB,GAAG,CAAC,CAAsC,EAAE,EAAE;QACpE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAChD,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CACpD,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,cACE,SAAS,EAAC,wKAAwK,EAClL,OAAO,EAAE,cAAc,YAEtB,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAC9D,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC9B,eAEE,SAAS,EAAC,+DAA+D,YAExE,MAAM,IAHF,MAAM,CAIN,CACR,CAAC,CACH,CAAC,CAAC,CAAC,CACF,eAAM,SAAS,EAAC,uBAAuB,+BAAsB,CAC9D,GACG,EACL,MAAM,IAAI,CACT,eAAK,SAAS,EAAC,oFAAoF,aACjG,gBACE,IAAI,EAAC,MAAM,EACX,SAAS,EAAC,2CAA2C,EACrD,WAAW,EAAC,mBAAmB,EAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,kBAAkB,GAC5B,EACF,cAAK,SAAS,EAAC,0BAA0B,YACtC,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC/B,eAEE,SAAS,EAAC,0CAA0C,EACpD,OAAO,EAAE,GAAG,EAAE;gCACZ,IAAI,CAAC,QAAQ;oCAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;4BAC3C,CAAC,aAED,gBACE,IAAI,EAAC,UAAU,EACf,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EACzC,QAAQ,QACR,SAAS,EAAC,MAAM,GAChB,EACF,eAAM,SAAS,EAAC,MAAM,YAAE,MAAM,GAAQ,KAZjC,MAAM,CAaP,CACP,CAAC,GACE,IACF,CACP,IACG,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Attribute, Component, Condition, DataJSON } from "../util/type";
|
|
3
|
-
import { ShowAttr } from "./ShowAttr";
|
|
4
|
-
import PrimaryBtn from "./PrimaryBtn";
|
|
5
|
-
import ShowErrors from "./ShowErrors";
|
|
6
|
-
|
|
7
|
-
type ComponentDisplayerProps = {
|
|
8
|
-
component: Component;
|
|
9
|
-
data: DataJSON;
|
|
10
|
-
setData: (data: DataJSON) => void;
|
|
11
|
-
onSubmit?: () => void;
|
|
12
|
-
disabled?: boolean;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function ComponentDisplayer({
|
|
16
|
-
component,
|
|
17
|
-
data,
|
|
18
|
-
setData,
|
|
19
|
-
onSubmit,
|
|
20
|
-
disabled,
|
|
21
|
-
}: ComponentDisplayerProps) {
|
|
22
|
-
const [errors, setErrors] = useState<string[]>([]);
|
|
23
|
-
return (
|
|
24
|
-
<form
|
|
25
|
-
onSubmit={(e) => {
|
|
26
|
-
e.preventDefault();
|
|
27
|
-
const errs = checkRequired(component.attributes, data);
|
|
28
|
-
if (errs.length === 0 && onSubmit) {
|
|
29
|
-
onSubmit();
|
|
30
|
-
} else {
|
|
31
|
-
setErrors(errs);
|
|
32
|
-
}
|
|
33
|
-
}}
|
|
34
|
-
className="min-w-72"
|
|
35
|
-
>
|
|
36
|
-
<h1 className="font-bold text-center text-blue-800 text-4xl mb-8">
|
|
37
|
-
{component.label}
|
|
38
|
-
</h1>
|
|
39
|
-
<div className="space-y-3">
|
|
40
|
-
{component.attributes.map((attr) => {
|
|
41
|
-
if (canShow(attr, data, component.conditions || [])) {
|
|
42
|
-
return (
|
|
43
|
-
<ShowAttr
|
|
44
|
-
key={attr.name}
|
|
45
|
-
attribute={attr}
|
|
46
|
-
value={data[attr.name] ? String(data[attr.name]) : ""}
|
|
47
|
-
setValue={(s) => {
|
|
48
|
-
const temp = { ...data };
|
|
49
|
-
temp[attr.name] = s;
|
|
50
|
-
setData(temp);
|
|
51
|
-
}}
|
|
52
|
-
data={data}
|
|
53
|
-
disabled={disabled}
|
|
54
|
-
/>
|
|
55
|
-
);
|
|
56
|
-
} else {
|
|
57
|
-
if (data[attr.name]) {
|
|
58
|
-
const temp = { ...data };
|
|
59
|
-
temp[attr.name] = "";
|
|
60
|
-
setData(temp);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
})}
|
|
64
|
-
</div>
|
|
65
|
-
{errors.length !== 0 && (
|
|
66
|
-
<div className="my-6">
|
|
67
|
-
<ShowErrors errors={errors} />
|
|
68
|
-
</div>
|
|
69
|
-
)}
|
|
70
|
-
{onSubmit && (
|
|
71
|
-
<div className="flex justify-center my-8">
|
|
72
|
-
<PrimaryBtn text="Submit" />
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
</form>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// check the required attributes and return array string of errors
|
|
80
|
-
function checkRequired(attributes: Attribute[], data: DataJSON): string[] {
|
|
81
|
-
const requiredAttributes = attributes.filter(
|
|
82
|
-
(attr) => attr.settings?.required,
|
|
83
|
-
);
|
|
84
|
-
const errors: string[] = [];
|
|
85
|
-
requiredAttributes.forEach((attr) => {
|
|
86
|
-
if (!data[attr.name]) {
|
|
87
|
-
errors.push(`${attr.label} is required`);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
return errors;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function canShow(
|
|
94
|
-
attr: Attribute,
|
|
95
|
-
data: DataJSON,
|
|
96
|
-
conds: Condition[][],
|
|
97
|
-
): boolean {
|
|
98
|
-
const conditions = conds.filter(
|
|
99
|
-
(c) => c[c.length - 1].delimiter === attr.name,
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
if (!conditions || conditions.length === 0) {
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
return conditions.some((conditionSet) => {
|
|
106
|
-
for (let i = 0; i < conditionSet.length; i++) {
|
|
107
|
-
const currentCond = conditionSet[i];
|
|
108
|
-
const result = evaluateCondition(currentCond, data);
|
|
109
|
-
if (currentCond.delimiter === "ou") {
|
|
110
|
-
if (result) {
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
} else if (currentCond.delimiter === "et") {
|
|
114
|
-
if (!result) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function evaluateCondition(condition: Condition, data: DataJSON): boolean {
|
|
125
|
-
const { attribute, cond, value } = condition;
|
|
126
|
-
const dataValue = data[attribute];
|
|
127
|
-
|
|
128
|
-
switch (cond) {
|
|
129
|
-
case "EQ":
|
|
130
|
-
return value === dataValue;
|
|
131
|
-
case "INF":
|
|
132
|
-
return value < dataValue;
|
|
133
|
-
case "SUP":
|
|
134
|
-
return value > dataValue;
|
|
135
|
-
case "CONTAIN":
|
|
136
|
-
return String(dataValue).includes(value);
|
|
137
|
-
default:
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { useRef } from "react";
|
|
2
|
-
|
|
3
|
-
type props = {
|
|
4
|
-
text: string;
|
|
5
|
-
disabled?: boolean;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export default function PrimaryBtn({ text, disabled }: props) {
|
|
9
|
-
const btn = useRef<HTMLButtonElement>(null);
|
|
10
|
-
return (
|
|
11
|
-
<button
|
|
12
|
-
type="submit"
|
|
13
|
-
ref={btn}
|
|
14
|
-
className={
|
|
15
|
-
"rounded-2xl bg-[#0096DC] bg-opacity-85 px-8 py-2 font-bold text-white hover:bg-opacity-100"
|
|
16
|
-
}
|
|
17
|
-
disabled={disabled}
|
|
18
|
-
>
|
|
19
|
-
{text}
|
|
20
|
-
</button>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { Attribute, AttributeTypeEnum, Content, DataJSON } from "../util/type";
|
|
2
|
-
import LabeledSelect from "./inputs/LableledSelect";
|
|
3
|
-
import LabeledInput from "./inputs/LableledInput";
|
|
4
|
-
import LabeledMutliSelect from "./inputs/LabeledMutliSelect";
|
|
5
|
-
import { useEffect, useMemo, useState } from "react";
|
|
6
|
-
import { Parser } from "expr-eval";
|
|
7
|
-
import LabeledFileInput from "./inputs/LableledFileInput";
|
|
8
|
-
|
|
9
|
-
type ShowAttrProps = {
|
|
10
|
-
attribute: Attribute;
|
|
11
|
-
value: string;
|
|
12
|
-
setValue: (s: string) => void;
|
|
13
|
-
data: DataJSON;
|
|
14
|
-
showLabel?: boolean;
|
|
15
|
-
disabled?: boolean;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function ShowAttr({
|
|
19
|
-
attribute,
|
|
20
|
-
value,
|
|
21
|
-
setValue,
|
|
22
|
-
showLabel = true,
|
|
23
|
-
data,
|
|
24
|
-
disabled,
|
|
25
|
-
}: ShowAttrProps) {
|
|
26
|
-
const label = useMemo(
|
|
27
|
-
() => (showLabel ? attribute.label || "" : ""),
|
|
28
|
-
[attribute.label]
|
|
29
|
-
);
|
|
30
|
-
const type = useMemo(() => {
|
|
31
|
-
if (attribute.type) return AttributeTypeEnum[attribute.type];
|
|
32
|
-
}, [attribute.type]);
|
|
33
|
-
switch (type) {
|
|
34
|
-
case AttributeTypeEnum.NUMBER:
|
|
35
|
-
return (
|
|
36
|
-
<LabeledInput
|
|
37
|
-
label={label}
|
|
38
|
-
value={value}
|
|
39
|
-
setValue={setValue}
|
|
40
|
-
type="number"
|
|
41
|
-
max={
|
|
42
|
-
attribute.settings?.max ? Number(attribute.settings.max) : undefined
|
|
43
|
-
}
|
|
44
|
-
min={
|
|
45
|
-
attribute.settings?.min ? Number(attribute.settings.min) : undefined
|
|
46
|
-
}
|
|
47
|
-
disabled={disabled}
|
|
48
|
-
/>
|
|
49
|
-
);
|
|
50
|
-
case AttributeTypeEnum.TEXT:
|
|
51
|
-
return (
|
|
52
|
-
<LabeledInput
|
|
53
|
-
label={label}
|
|
54
|
-
value={value}
|
|
55
|
-
setValue={setValue}
|
|
56
|
-
type="text"
|
|
57
|
-
maxLength={attribute.settings?.max}
|
|
58
|
-
minLength={attribute.settings?.min}
|
|
59
|
-
disabled={disabled}
|
|
60
|
-
/>
|
|
61
|
-
);
|
|
62
|
-
case AttributeTypeEnum.LIST:
|
|
63
|
-
return (
|
|
64
|
-
<LabeledSelect
|
|
65
|
-
label={label}
|
|
66
|
-
selected={value}
|
|
67
|
-
setSelected={setValue}
|
|
68
|
-
options={(attribute.content as string[]) || []}
|
|
69
|
-
disabled={disabled}
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
case AttributeTypeEnum.BOOLEAN:
|
|
73
|
-
return (
|
|
74
|
-
<LabeledInput
|
|
75
|
-
type="checkbox"
|
|
76
|
-
label={label}
|
|
77
|
-
checked={value === "true"}
|
|
78
|
-
setValue={(s) => setValue(s)}
|
|
79
|
-
disabled={disabled}
|
|
80
|
-
/>
|
|
81
|
-
);
|
|
82
|
-
case AttributeTypeEnum.DATE:
|
|
83
|
-
return (
|
|
84
|
-
<LabeledInput
|
|
85
|
-
type="date"
|
|
86
|
-
label={label}
|
|
87
|
-
value={value}
|
|
88
|
-
setValue={(date) => setValue(date)}
|
|
89
|
-
disabled={disabled}
|
|
90
|
-
/>
|
|
91
|
-
);
|
|
92
|
-
case AttributeTypeEnum.LINK:
|
|
93
|
-
return (
|
|
94
|
-
<div className="w-full">
|
|
95
|
-
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
96
|
-
<a href={attribute.content as string} target="_blank">
|
|
97
|
-
{attribute.content}
|
|
98
|
-
</a>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
case AttributeTypeEnum.MULTIPLE:
|
|
102
|
-
return (
|
|
103
|
-
<LabeledMutliSelect
|
|
104
|
-
label={label}
|
|
105
|
-
selectedOptions={value !== "" ? value.split(",") : []}
|
|
106
|
-
onChange={(selected) => setValue(selected.join(","))}
|
|
107
|
-
options={(attribute.content as string[]) || []}
|
|
108
|
-
disabled={disabled}
|
|
109
|
-
/>
|
|
110
|
-
);
|
|
111
|
-
case AttributeTypeEnum.FILE:
|
|
112
|
-
case AttributeTypeEnum.IMAGE:
|
|
113
|
-
return (
|
|
114
|
-
<LabeledFileInput
|
|
115
|
-
label={label}
|
|
116
|
-
fileUrl={value.includes("http") ? value : undefined}
|
|
117
|
-
setFileData={setValue}
|
|
118
|
-
disabled={disabled}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
case AttributeTypeEnum.IMAGE_OUTPUT:
|
|
122
|
-
return (
|
|
123
|
-
<div className="w-full">
|
|
124
|
-
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
125
|
-
<img className="w-64" src={attribute.content as string} />
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
case AttributeTypeEnum.VIDEO_OUTPUT:
|
|
129
|
-
return <ShowVideo url={attribute.content as string} />;
|
|
130
|
-
case AttributeTypeEnum.TEXT_OUTPUT:
|
|
131
|
-
return (
|
|
132
|
-
<div className="w-full">
|
|
133
|
-
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
134
|
-
<textarea className="w-full bg-white" disabled>
|
|
135
|
-
{attribute.content as string}
|
|
136
|
-
</textarea>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
case AttributeTypeEnum.TITRE:
|
|
140
|
-
return (
|
|
141
|
-
<div className="w-full">
|
|
142
|
-
<h2 className="text-3xl font-semibold">{attribute.label}</h2>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
case AttributeTypeEnum.AUTOGEN:
|
|
146
|
-
let result = 0;
|
|
147
|
-
if (attribute.content && typeof attribute.content === "string") {
|
|
148
|
-
result = calculateContent(attribute.content, data);
|
|
149
|
-
return <p>{result}</p>;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function calculateContent(content: string, data: DataJSON): number {
|
|
155
|
-
const parser = new Parser();
|
|
156
|
-
const expr = parser.parse(content);
|
|
157
|
-
const vars = expr.variables();
|
|
158
|
-
if (vars.every((v) => !!data[v])) {
|
|
159
|
-
return expr.evaluate(data as { [propertyName: string]: string }) as number;
|
|
160
|
-
} else {
|
|
161
|
-
return 0;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function ShowVideo({ url }: { url: string }) {
|
|
166
|
-
return (
|
|
167
|
-
<div>
|
|
168
|
-
<iframe
|
|
169
|
-
width="560"
|
|
170
|
-
height="315"
|
|
171
|
-
src={"https://www.youtube.com/embed/" + url}
|
|
172
|
-
title="YouTube video player"
|
|
173
|
-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
174
|
-
></iframe>
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import WrongIcon from "./icons/WrongIcon";
|
|
2
|
-
|
|
3
|
-
export default function ShowErrors({ errors }: { errors: string[] }) {
|
|
4
|
-
return (
|
|
5
|
-
<div
|
|
6
|
-
className="bg-red-50 border border-red-300 text-red-600 px-3 py-2 rounded-xl relative"
|
|
7
|
-
role="alert"
|
|
8
|
-
>
|
|
9
|
-
<div className="flex items-center gap-2">
|
|
10
|
-
<WrongIcon />
|
|
11
|
-
<span className="font-semibold">{errors.length} error found.</span>
|
|
12
|
-
</div>
|
|
13
|
-
<ul className="mt-2 ml-7 list-disc list-inside">
|
|
14
|
-
{errors.map((error) => (
|
|
15
|
-
<li>{error}</li>
|
|
16
|
-
))}
|
|
17
|
-
</ul>
|
|
18
|
-
</div>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export default function WrongIcon() {
|
|
2
|
-
return (
|
|
3
|
-
<svg
|
|
4
|
-
fill="#c01c28"
|
|
5
|
-
width="22px"
|
|
6
|
-
height="22px"
|
|
7
|
-
viewBox="0 0 200 200"
|
|
8
|
-
data-name="Layer 1"
|
|
9
|
-
id="Layer_1"
|
|
10
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
-
stroke="#c01c28"
|
|
12
|
-
>
|
|
13
|
-
<g id="SVGRepo_bgCarrier" stroke-width="0" />
|
|
14
|
-
|
|
15
|
-
<g
|
|
16
|
-
id="SVGRepo_tracerCarrier"
|
|
17
|
-
stroke-linecap="round"
|
|
18
|
-
stroke-linejoin="round"
|
|
19
|
-
/>
|
|
20
|
-
|
|
21
|
-
<g id="SVGRepo_iconCarrier">
|
|
22
|
-
<title />
|
|
23
|
-
|
|
24
|
-
<path d="M100,15a85,85,0,1,0,85,85A84.93,84.93,0,0,0,100,15Zm0,150a65,65,0,1,1,65-65A64.87,64.87,0,0,1,100,165Z" />
|
|
25
|
-
|
|
26
|
-
<path d="M128.5,74a9.67,9.67,0,0,0-14,0L100,88.5l-14-14a9.9,9.9,0,0,0-14,14l14,14-14,14a9.9,9.9,0,0,0,14,14l14-14,14,14a9.9,9.9,0,0,0,14-14l-14-14,14-14A10.77,10.77,0,0,0,128.5,74Z" />
|
|
27
|
-
</g>
|
|
28
|
-
</svg>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { ChangeEvent } from "react";
|
|
2
|
-
|
|
3
|
-
export type FileInputProps = {
|
|
4
|
-
fileUrl?: string;
|
|
5
|
-
setFileData?: (fileData: string) => void;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default function FileInput({
|
|
10
|
-
fileUrl,
|
|
11
|
-
setFileData,
|
|
12
|
-
disabled,
|
|
13
|
-
}: FileInputProps) {
|
|
14
|
-
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
|
15
|
-
if (event.target.files && event.target.files[0] && setFileData) {
|
|
16
|
-
const reader = new FileReader();
|
|
17
|
-
reader.onloadend = () => {
|
|
18
|
-
setFileData(
|
|
19
|
-
`${event.target.files![0].name}&&${reader.result as string}`,
|
|
20
|
-
);
|
|
21
|
-
};
|
|
22
|
-
reader.readAsDataURL(event.target.files[0]);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return (
|
|
26
|
-
<div>
|
|
27
|
-
{fileUrl && (
|
|
28
|
-
<a href={fileUrl} target="_blank" className="text-blue-500 block">
|
|
29
|
-
{fileUrl.substring(fileUrl.lastIndexOf("/") + 1)}
|
|
30
|
-
</a>
|
|
31
|
-
)}
|
|
32
|
-
{setFileData && !disabled && (
|
|
33
|
-
<input type="file" onChange={handleChange} />
|
|
34
|
-
)}
|
|
35
|
-
</div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { HTMLInputTypeAttribute } from "react";
|
|
2
|
-
|
|
3
|
-
export type InputProps = {
|
|
4
|
-
value?: string;
|
|
5
|
-
setValue: (s: string) => void;
|
|
6
|
-
type: HTMLInputTypeAttribute;
|
|
7
|
-
onBlur?: () => void;
|
|
8
|
-
maxLength?: string;
|
|
9
|
-
minLength?: string;
|
|
10
|
-
max?: number;
|
|
11
|
-
min?: number;
|
|
12
|
-
placeholder?: string;
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
checked?: boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export default function Input({
|
|
18
|
-
value,
|
|
19
|
-
setValue,
|
|
20
|
-
type,
|
|
21
|
-
onBlur,
|
|
22
|
-
maxLength,
|
|
23
|
-
minLength,
|
|
24
|
-
max,
|
|
25
|
-
min,
|
|
26
|
-
placeholder,
|
|
27
|
-
disabled,
|
|
28
|
-
checked,
|
|
29
|
-
}: InputProps) {
|
|
30
|
-
return (
|
|
31
|
-
<input
|
|
32
|
-
type={type}
|
|
33
|
-
className={`py-1.5 px-3 ${type === "checkbox" ? "size-5" : "w-full"} border-2 border-gray-300 rounded-xl text-base focus:border-blue-500 focus:ring-blue-500 disabled:bg-gray-400 disabled:opacity-50 disabled:pointer-events-none`}
|
|
34
|
-
value={value}
|
|
35
|
-
onChange={(e) =>
|
|
36
|
-
type === "checkbox"
|
|
37
|
-
? setValue(String(e.target.checked))
|
|
38
|
-
: setValue(e.target.value)
|
|
39
|
-
}
|
|
40
|
-
disabled={disabled}
|
|
41
|
-
onBlur={onBlur}
|
|
42
|
-
maxLength={
|
|
43
|
-
maxLength && !isNaN(Number(maxLength)) ? Number(maxLength) : undefined
|
|
44
|
-
}
|
|
45
|
-
minLength={
|
|
46
|
-
minLength && !isNaN(Number(minLength)) ? Number(minLength) : undefined
|
|
47
|
-
}
|
|
48
|
-
max={max}
|
|
49
|
-
min={min}
|
|
50
|
-
placeholder={placeholder}
|
|
51
|
-
checked={checked}
|
|
52
|
-
/>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import Input, { InputProps } from "./Input";
|
|
2
|
-
import MultiSelect, { MultiSelectProps } from "./MultiSelect";
|
|
3
|
-
|
|
4
|
-
type Props = {
|
|
5
|
-
label: string;
|
|
6
|
-
} & MultiSelectProps;
|
|
7
|
-
|
|
8
|
-
export default function LabeledMutliSelect({ label, ...inputprops }: Props) {
|
|
9
|
-
return (
|
|
10
|
-
<div className="w-full">
|
|
11
|
-
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
12
|
-
<MultiSelect {...inputprops} />
|
|
13
|
-
</div>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import FileInput, { FileInputProps } from "./FileInput";
|
|
2
|
-
|
|
3
|
-
type Props = {
|
|
4
|
-
label: string;
|
|
5
|
-
} & FileInputProps;
|
|
6
|
-
|
|
7
|
-
export default function LabeledFileInput({ label, ...inputProps }: Props) {
|
|
8
|
-
return (
|
|
9
|
-
<div className="w-full flex gap-2 items-center">
|
|
10
|
-
<label className="block text-base font-bold mb-1">{label}</label>
|
|
11
|
-
<FileInput {...inputProps} />
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import Input, { InputProps } from "./Input";
|
|
2
|
-
|
|
3
|
-
type Props = {
|
|
4
|
-
label: string;
|
|
5
|
-
} & InputProps;
|
|
6
|
-
|
|
7
|
-
export default function LabeledInput({ label, ...inputProps }: Props) {
|
|
8
|
-
return (
|
|
9
|
-
<div className="w-full flex gap-2 items-center">
|
|
10
|
-
<label className="block text-base font-bold mb-1">{label}</label>
|
|
11
|
-
<Input {...inputProps} />
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import Select, { SelectProps } from "./Select";
|
|
2
|
-
|
|
3
|
-
type Props = {
|
|
4
|
-
label: string;
|
|
5
|
-
} & SelectProps;
|
|
6
|
-
|
|
7
|
-
export default function LabeledSelect({ label, ...inputProps }: Props) {
|
|
8
|
-
return (
|
|
9
|
-
<div className="w-full">
|
|
10
|
-
<label className="block text-sm font-bold mb-1">{label}</label>
|
|
11
|
-
<Select {...inputProps} />
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
|
|
3
|
-
export type MultiSelectProps = {
|
|
4
|
-
options: string[];
|
|
5
|
-
selectedOptions: string[];
|
|
6
|
-
onChange: (selected: string[]) => void;
|
|
7
|
-
disabled?: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const MultiSelect: React.FC<MultiSelectProps> = ({
|
|
11
|
-
options,
|
|
12
|
-
selectedOptions,
|
|
13
|
-
onChange,
|
|
14
|
-
disabled,
|
|
15
|
-
}) => {
|
|
16
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
17
|
-
const [filter, setFilter] = useState("");
|
|
18
|
-
|
|
19
|
-
const toggleDropdown = () => setIsOpen(!isOpen);
|
|
20
|
-
|
|
21
|
-
const handleOptionClick = (option: string) => {
|
|
22
|
-
if (selectedOptions.includes(option)) {
|
|
23
|
-
onChange(selectedOptions.filter((selected) => selected !== option));
|
|
24
|
-
} else {
|
|
25
|
-
onChange([...selectedOptions, option]);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const handleFilterChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
29
|
-
setFilter(e.target.value);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const filteredOptions = options.filter((option) =>
|
|
33
|
-
option.toLowerCase().includes(filter.toLowerCase()),
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className="relative inline-block w-full">
|
|
38
|
-
<div
|
|
39
|
-
className="py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
|
|
40
|
-
onClick={toggleDropdown}
|
|
41
|
-
>
|
|
42
|
-
{Array.isArray(selectedOptions) && selectedOptions.length > 0 ? (
|
|
43
|
-
selectedOptions.map((option) => (
|
|
44
|
-
<span
|
|
45
|
-
key={option}
|
|
46
|
-
className="mr-1 inline-block rounded-full bg-secondary-blue px-2 py-1 text-sm"
|
|
47
|
-
>
|
|
48
|
-
{option}
|
|
49
|
-
</span>
|
|
50
|
-
))
|
|
51
|
-
) : (
|
|
52
|
-
<span className="text-placeholder-gray">Select options</span>
|
|
53
|
-
)}
|
|
54
|
-
</div>
|
|
55
|
-
{isOpen && (
|
|
56
|
-
<div className="absolute left-0 right-0 z-10 mt-2 rounded-md border border-secondary-gray bg-white">
|
|
57
|
-
<input
|
|
58
|
-
type="text"
|
|
59
|
-
className="w-full border-b border-secondary-gray p-2"
|
|
60
|
-
placeholder="Filter options..."
|
|
61
|
-
value={filter}
|
|
62
|
-
onChange={handleFilterChange}
|
|
63
|
-
/>
|
|
64
|
-
<div className="max-h-60 overflow-y-auto">
|
|
65
|
-
{filteredOptions.map((option) => (
|
|
66
|
-
<div
|
|
67
|
-
key={option}
|
|
68
|
-
className="cursor-pointer p-2 hover:bg-primary-gray"
|
|
69
|
-
onClick={() => {
|
|
70
|
-
if (!disabled) handleOptionClick(option);
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
73
|
-
<input
|
|
74
|
-
type="checkbox"
|
|
75
|
-
checked={selectedOptions.includes(option)}
|
|
76
|
-
readOnly
|
|
77
|
-
className="mr-2"
|
|
78
|
-
/>
|
|
79
|
-
<span className="ml-2">{option}</span>
|
|
80
|
-
</div>
|
|
81
|
-
))}
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export default MultiSelect;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export type SelectProps = {
|
|
2
|
-
options: string[];
|
|
3
|
-
selected: string;
|
|
4
|
-
setSelected: (s: string) => void;
|
|
5
|
-
canDefault?: boolean;
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export default function Select({
|
|
10
|
-
options,
|
|
11
|
-
selected,
|
|
12
|
-
setSelected,
|
|
13
|
-
canDefault,
|
|
14
|
-
disabled,
|
|
15
|
-
}: SelectProps) {
|
|
16
|
-
return (
|
|
17
|
-
<select
|
|
18
|
-
value={selected || ""}
|
|
19
|
-
onChange={(e) => setSelected(e.target.value)}
|
|
20
|
-
className="py-2 px-3 bg-white block w-full border-2 border-gray-200 rounded-lg text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none"
|
|
21
|
-
disabled={disabled}
|
|
22
|
-
>
|
|
23
|
-
<option value="" disabled={!canDefault} hidden={!canDefault}>
|
|
24
|
-
selection
|
|
25
|
-
</option>
|
|
26
|
-
{options.map((opt, i) => (
|
|
27
|
-
<option key={i} value={opt}>
|
|
28
|
-
{opt}
|
|
29
|
-
</option>
|
|
30
|
-
))}
|
|
31
|
-
</select>
|
|
32
|
-
);
|
|
33
|
-
}
|
package/src/index.css
DELETED
package/src/index.tsx
DELETED
package/src/util/type.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
// Scalars
|
|
2
|
-
export type DataJSON = Record<string, string | object>;
|
|
3
|
-
export type DateTime = string;
|
|
4
|
-
|
|
5
|
-
// Enums
|
|
6
|
-
export enum ConditionEnum {
|
|
7
|
-
EQ = "equal",
|
|
8
|
-
SUP = "superior",
|
|
9
|
-
INF = "inferior",
|
|
10
|
-
CONTAIN = "contain",
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export enum AttributeTypeEnum {
|
|
14
|
-
TEXT = "text",
|
|
15
|
-
LIST = "list",
|
|
16
|
-
NUMBER = "number",
|
|
17
|
-
BOOLEAN = "boolean",
|
|
18
|
-
DATE = "date",
|
|
19
|
-
NOTE = "note",
|
|
20
|
-
IMAGE = "image",
|
|
21
|
-
LINK = "link",
|
|
22
|
-
MULTIPLE = "multiple",
|
|
23
|
-
AUTOGEN = "autogen",
|
|
24
|
-
FILE = "file",
|
|
25
|
-
TITRE = "titre",
|
|
26
|
-
TEXT_OUTPUT = "text_output",
|
|
27
|
-
IMAGE_OUTPUT = "image_output",
|
|
28
|
-
VIDEO_OUTPUT = "video_output",
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export enum ComponentType {
|
|
32
|
-
GENERAL = "general",
|
|
33
|
-
ACCESS = "access",
|
|
34
|
-
PEC = "PEC",
|
|
35
|
-
DOSSIER = "dossier",
|
|
36
|
-
PATIENT = "patient",
|
|
37
|
-
PRESCRIPTION = "prescription",
|
|
38
|
-
WORKFLOW = "workflow",
|
|
39
|
-
FOLDER = "folder",
|
|
40
|
-
CONSULTATION = "consultation",
|
|
41
|
-
TREE = "tree",
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Types
|
|
45
|
-
export interface Condition {
|
|
46
|
-
attribute: string;
|
|
47
|
-
cond: keyof typeof ConditionEnum;
|
|
48
|
-
value: string;
|
|
49
|
-
delimiter: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ISettingsAttribute {
|
|
53
|
-
max?: string;
|
|
54
|
-
min?: string;
|
|
55
|
-
color?: string;
|
|
56
|
-
required?: boolean;
|
|
57
|
-
newValue?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface Attribute {
|
|
61
|
-
name: string;
|
|
62
|
-
label?: string;
|
|
63
|
-
type?: keyof typeof AttributeTypeEnum;
|
|
64
|
-
content?: Content;
|
|
65
|
-
settings?: ISettingsAttribute;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export type Content = string | string[];
|
|
69
|
-
|
|
70
|
-
export interface SettingsComponent {
|
|
71
|
-
required?: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface Component {
|
|
75
|
-
_id: string;
|
|
76
|
-
name: string;
|
|
77
|
-
label: string;
|
|
78
|
-
type: keyof typeof ComponentType;
|
|
79
|
-
attributes: Attribute[];
|
|
80
|
-
conditions: Condition[][];
|
|
81
|
-
settings?: SettingsComponent;
|
|
82
|
-
filter?: PrescriptionFilterType;
|
|
83
|
-
createdAt?: DateTime;
|
|
84
|
-
updatedAt?: DateTime;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface ComponentResult {
|
|
88
|
-
total: number;
|
|
89
|
-
page: number;
|
|
90
|
-
lastPage: number;
|
|
91
|
-
data: Component[];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface Data {
|
|
95
|
-
_id: string;
|
|
96
|
-
proprietor: string;
|
|
97
|
-
from: string;
|
|
98
|
-
component: Component;
|
|
99
|
-
value: DataJSON;
|
|
100
|
-
extra: DataJSON;
|
|
101
|
-
createdAt: DateTime;
|
|
102
|
-
updatedAt: DateTime;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface DataResult {
|
|
106
|
-
total: number;
|
|
107
|
-
page: number;
|
|
108
|
-
lastPage: number;
|
|
109
|
-
data: Data[];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type PrescriptionFilterType = {
|
|
113
|
-
protocol: string | null;
|
|
114
|
-
cure: string | null;
|
|
115
|
-
molecule: string | null;
|
|
116
|
-
dose: Filter | null;
|
|
117
|
-
validation: Filter | null;
|
|
118
|
-
info: boolean | null;
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export type Filter = {
|
|
122
|
-
op: ConditionEnum | "";
|
|
123
|
-
value: string;
|
|
124
|
-
};
|