nucleus-core-ts 0.9.841 → 0.9.842
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/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +12 -4
- package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +14 -22
- package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +270 -280
- package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +98 -88
- package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +80 -90
- package/dist/fe/components/IntegrationsPage/components/Primitives.d.ts +17 -0
- package/dist/fe/components/IntegrationsPage/components/Primitives.js +37 -0
- package/dist/fe/components/IntegrationsPage/components/ReferenceCheckRows.js +14 -22
- package/dist/fe/components/IntegrationsPage/theme/index.d.ts +8 -0
- package/dist/fe/components/IntegrationsPage/theme/index.js +15 -0
- package/package.json +1 -1
|
@@ -18,8 +18,16 @@ import { integrationsPageTheme as theme } from '../theme';
|
|
|
18
18
|
*/ export function ConnectionSwitcher() {
|
|
19
19
|
const store = useIntegrationsStore();
|
|
20
20
|
const labels = useLabels();
|
|
21
|
+
/**
|
|
22
|
+
* The way back does not depend on finding anything.
|
|
23
|
+
*
|
|
24
|
+
* `store.sources` is the rail's CURRENT PAGE, and the rail is unmounted the
|
|
25
|
+
* moment a connection is chosen — so a selected connection that is not on
|
|
26
|
+
* that page rendered nothing at all here: no name, and no way back to the
|
|
27
|
+
* list short of reloading the page. The name is what needs looking up; the
|
|
28
|
+
* button is not.
|
|
29
|
+
*/ if (!store.selectedSourceId) return null;
|
|
21
30
|
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId);
|
|
22
|
-
if (!source) return null;
|
|
23
31
|
return /*#__PURE__*/ _jsxs("div", {
|
|
24
32
|
className: cn('flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200/80 bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900'),
|
|
25
33
|
children: [
|
|
@@ -32,12 +40,12 @@ import { integrationsPageTheme as theme } from '../theme';
|
|
|
32
40
|
}),
|
|
33
41
|
/*#__PURE__*/ _jsx("span", {
|
|
34
42
|
className: "truncate font-semibold text-base text-slate-900 dark:text-slate-100",
|
|
35
|
-
children: source.
|
|
43
|
+
children: source?.name ?? labels.connections.heading
|
|
36
44
|
}),
|
|
37
|
-
/*#__PURE__*/ _jsx("span", {
|
|
45
|
+
source?.baseUrl ? /*#__PURE__*/ _jsx("span", {
|
|
38
46
|
className: cn(theme.list.itemMeta, 'mt-0.5'),
|
|
39
47
|
children: source.baseUrl
|
|
40
|
-
})
|
|
48
|
+
}) : null
|
|
41
49
|
]
|
|
42
50
|
}),
|
|
43
51
|
/*#__PURE__*/ _jsx("button", {
|
|
@@ -6,7 +6,7 @@ import { useLabels } from '../text/context';
|
|
|
6
6
|
import { integrationsPageTheme } from '../theme';
|
|
7
7
|
import { FieldMappingRow } from './FieldMappingRow';
|
|
8
8
|
import { EMPTY_FIELD_MAPPING, FIELD_MAPPING_COLUMNS, FIELD_MAPPING_GRID, PARENT_PREFIX } from './fieldMappingOptions';
|
|
9
|
-
import { Notice } from './Primitives';
|
|
9
|
+
import { Notice, Section } from './Primitives';
|
|
10
10
|
/**
|
|
11
11
|
* The rows that say which incoming value becomes which column.
|
|
12
12
|
*
|
|
@@ -33,28 +33,20 @@ export function FieldMappingRows({ rows, sourceFields, targetFields, disabled, o
|
|
|
33
33
|
if (!moving || !displaced) return;
|
|
34
34
|
onChange(rows.with(index, displaced).with(target, moving));
|
|
35
35
|
};
|
|
36
|
-
return /*#__PURE__*/ _jsxs(
|
|
37
|
-
|
|
36
|
+
return /*#__PURE__*/ _jsxs(Section, {
|
|
37
|
+
action: /*#__PURE__*/ _jsx("button", {
|
|
38
|
+
className: theme.button.secondary,
|
|
39
|
+
disabled: disabled,
|
|
40
|
+
onClick: ()=>onChange([
|
|
41
|
+
...rows,
|
|
42
|
+
EMPTY_FIELD_MAPPING
|
|
43
|
+
]),
|
|
44
|
+
type: "button",
|
|
45
|
+
children: "Add field"
|
|
46
|
+
}),
|
|
47
|
+
purpose: "Which incoming value becomes which column. The order is the order they are applied in.",
|
|
48
|
+
title: labels.mappings.fieldsHeading,
|
|
38
49
|
children: [
|
|
39
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
40
|
-
className: "flex flex-wrap items-center justify-between gap-2",
|
|
41
|
-
children: [
|
|
42
|
-
/*#__PURE__*/ _jsx("h3", {
|
|
43
|
-
className: theme.card.title,
|
|
44
|
-
children: labels.mappings.fieldsHeading
|
|
45
|
-
}),
|
|
46
|
-
/*#__PURE__*/ _jsx("button", {
|
|
47
|
-
className: theme.button.secondary,
|
|
48
|
-
disabled: disabled,
|
|
49
|
-
onClick: ()=>onChange([
|
|
50
|
-
...rows,
|
|
51
|
-
EMPTY_FIELD_MAPPING
|
|
52
|
-
]),
|
|
53
|
-
type: "button",
|
|
54
|
-
children: "Add field"
|
|
55
|
-
})
|
|
56
|
-
]
|
|
57
|
-
}),
|
|
58
50
|
sourceFields.length === 0 ? /*#__PURE__*/ _jsx(Notice, {
|
|
59
51
|
title: labels.mappings.sampleFirstTitle,
|
|
60
52
|
tone: "warning",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { integrationsPageTheme } from '../theme';
|
|
5
|
-
import { Field, Notice } from './Primitives';
|
|
5
|
+
import { Field, Notice, Section } from './Primitives';
|
|
6
6
|
/**
|
|
7
7
|
* A login for every person this import brings in.
|
|
8
8
|
*
|
|
@@ -45,300 +45,290 @@ export function MappingAccountFields({ value, targetFields, targets, disabled, o
|
|
|
45
45
|
passwordColumn: ''
|
|
46
46
|
});
|
|
47
47
|
};
|
|
48
|
-
return /*#__PURE__*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
children: "Not set"
|
|
137
|
-
}),
|
|
138
|
-
accountFields.filter((field)=>PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
139
|
-
value: field,
|
|
140
|
-
children: field
|
|
141
|
-
}, field)),
|
|
142
|
-
accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).length > 0 ? /*#__PURE__*/ _jsx("optgroup", {
|
|
143
|
-
label: "Other columns",
|
|
144
|
-
children: accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
145
|
-
value: field,
|
|
146
|
-
children: field
|
|
147
|
-
}, field))
|
|
148
|
-
}) : null
|
|
149
|
-
]
|
|
150
|
-
})
|
|
151
|
-
}),
|
|
152
|
-
/*#__PURE__*/ _jsx(Field, {
|
|
153
|
-
hint: "The imported record's column carrying that same value.",
|
|
154
|
-
htmlFor: `${uid}-match-from`,
|
|
155
|
-
label: "Matched from",
|
|
156
|
-
children: /*#__PURE__*/ _jsxs("select", {
|
|
157
|
-
className: theme.field.input,
|
|
158
|
-
disabled: disabled,
|
|
159
|
-
id: `${uid}-match-from`,
|
|
160
|
-
onChange: (event)=>patch({
|
|
161
|
-
matchFrom: event.target.value
|
|
162
|
-
}),
|
|
163
|
-
value: value.matchFrom,
|
|
164
|
-
children: [
|
|
165
|
-
/*#__PURE__*/ _jsx("option", {
|
|
166
|
-
value: "",
|
|
167
|
-
children: "Not set"
|
|
168
|
-
}),
|
|
169
|
-
targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
48
|
+
return /*#__PURE__*/ _jsx(Section, {
|
|
49
|
+
action: /*#__PURE__*/ _jsx("button", {
|
|
50
|
+
className: theme.button.secondary,
|
|
51
|
+
disabled: disabled || targetFields.length === 0,
|
|
52
|
+
onClick: ()=>value ? onChange(null) : turnOn(),
|
|
53
|
+
type: "button",
|
|
54
|
+
children: value ? 'Turn off' : 'Turn on'
|
|
55
|
+
}),
|
|
56
|
+
purpose: "A directory carries people, not logins. Turned on, the account is created in the same import and linked to the record.",
|
|
57
|
+
title: "Also create a login for each new record",
|
|
58
|
+
children: value ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
59
|
+
children: [
|
|
60
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
61
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3",
|
|
62
|
+
children: [
|
|
63
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
64
|
+
htmlFor: `${uid}-entity`,
|
|
65
|
+
label: "Accounts live in",
|
|
66
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
67
|
+
className: theme.field.input,
|
|
68
|
+
disabled: disabled,
|
|
69
|
+
id: `${uid}-entity`,
|
|
70
|
+
onChange: (event)=>patch({
|
|
71
|
+
entity: event.target.value,
|
|
72
|
+
passwordColumn: '',
|
|
73
|
+
matchColumn: 'email'
|
|
74
|
+
}),
|
|
75
|
+
value: value.entity,
|
|
76
|
+
children: [
|
|
77
|
+
/*#__PURE__*/ _jsx("option", {
|
|
78
|
+
value: "",
|
|
79
|
+
children: "Not set"
|
|
80
|
+
}),
|
|
81
|
+
targets.map((target)=>/*#__PURE__*/ _jsx("option", {
|
|
82
|
+
value: target.entity,
|
|
83
|
+
children: target.label
|
|
84
|
+
}, target.entity))
|
|
85
|
+
]
|
|
86
|
+
})
|
|
87
|
+
}),
|
|
88
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
89
|
+
hint: "The column on the account that identifies it.",
|
|
90
|
+
htmlFor: `${uid}-match-column`,
|
|
91
|
+
label: "Identified by",
|
|
92
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
93
|
+
className: theme.field.input,
|
|
94
|
+
disabled: disabled || accountFields.length === 0,
|
|
95
|
+
id: `${uid}-match-column`,
|
|
96
|
+
onChange: (event)=>patch({
|
|
97
|
+
matchColumn: event.target.value
|
|
98
|
+
}),
|
|
99
|
+
value: value.matchColumn,
|
|
100
|
+
children: [
|
|
101
|
+
/*#__PURE__*/ _jsx("option", {
|
|
102
|
+
value: "",
|
|
103
|
+
children: "Not set"
|
|
104
|
+
}),
|
|
105
|
+
accountFields.map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
106
|
+
value: field,
|
|
107
|
+
children: field
|
|
108
|
+
}, field))
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
}),
|
|
112
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
113
|
+
hint: "Where the run's shared password is written.",
|
|
114
|
+
htmlFor: `${uid}-password`,
|
|
115
|
+
label: "Password goes into",
|
|
116
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
117
|
+
className: theme.field.input,
|
|
118
|
+
disabled: disabled || accountFields.length === 0,
|
|
119
|
+
id: `${uid}-password`,
|
|
120
|
+
onChange: (event)=>patch({
|
|
121
|
+
passwordColumn: event.target.value
|
|
122
|
+
}),
|
|
123
|
+
value: value.passwordColumn,
|
|
124
|
+
children: [
|
|
125
|
+
/*#__PURE__*/ _jsx("option", {
|
|
126
|
+
value: "",
|
|
127
|
+
children: "Not set"
|
|
128
|
+
}),
|
|
129
|
+
accountFields.filter((field)=>PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
130
|
+
value: field,
|
|
131
|
+
children: field
|
|
132
|
+
}, field)),
|
|
133
|
+
accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).length > 0 ? /*#__PURE__*/ _jsx("optgroup", {
|
|
134
|
+
label: "Other columns",
|
|
135
|
+
children: accountFields.filter((field)=>!PASSWORD_LIKE.test(field)).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
170
136
|
value: field,
|
|
171
137
|
children: field
|
|
172
138
|
}, field))
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
139
|
+
}) : null
|
|
140
|
+
]
|
|
141
|
+
})
|
|
142
|
+
}),
|
|
143
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
144
|
+
hint: "The imported record's column carrying that same value.",
|
|
145
|
+
htmlFor: `${uid}-match-from`,
|
|
146
|
+
label: "Matched from",
|
|
147
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
148
|
+
className: theme.field.input,
|
|
149
|
+
disabled: disabled,
|
|
150
|
+
id: `${uid}-match-from`,
|
|
151
|
+
onChange: (event)=>patch({
|
|
152
|
+
matchFrom: event.target.value
|
|
153
|
+
}),
|
|
154
|
+
value: value.matchFrom,
|
|
155
|
+
children: [
|
|
156
|
+
/*#__PURE__*/ _jsx("option", {
|
|
157
|
+
value: "",
|
|
158
|
+
children: "Not set"
|
|
159
|
+
}),
|
|
160
|
+
targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
161
|
+
value: field,
|
|
162
|
+
children: field
|
|
163
|
+
}, field))
|
|
164
|
+
]
|
|
165
|
+
})
|
|
166
|
+
}),
|
|
167
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
168
|
+
hint: "Filled with the new account's id, so the record and its login know each other.",
|
|
169
|
+
htmlFor: `${uid}-id-column`,
|
|
170
|
+
label: "Account id goes into",
|
|
171
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
172
|
+
className: theme.field.input,
|
|
173
|
+
disabled: disabled,
|
|
174
|
+
id: `${uid}-id-column`,
|
|
175
|
+
onChange: (event)=>patch({
|
|
176
|
+
idColumn: event.target.value
|
|
177
|
+
}),
|
|
178
|
+
value: value.idColumn,
|
|
179
|
+
children: [
|
|
180
|
+
/*#__PURE__*/ _jsx("option", {
|
|
181
|
+
value: "",
|
|
182
|
+
children: "Not set"
|
|
183
|
+
}),
|
|
184
|
+
targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
185
|
+
value: field,
|
|
186
|
+
children: field
|
|
187
|
+
}, field))
|
|
188
|
+
]
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
194
|
+
className: "flex flex-col gap-2",
|
|
195
|
+
children: [
|
|
196
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
197
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
198
|
+
children: [
|
|
199
|
+
/*#__PURE__*/ _jsx("span", {
|
|
200
|
+
className: theme.field.label,
|
|
201
|
+
children: "What each new account is a member of"
|
|
202
|
+
}),
|
|
203
|
+
/*#__PURE__*/ _jsx("button", {
|
|
204
|
+
className: theme.button.ghost,
|
|
182
205
|
disabled: disabled,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
206
|
+
onClick: ()=>patch({
|
|
207
|
+
joins: [
|
|
208
|
+
...value.joins ?? [],
|
|
209
|
+
{
|
|
210
|
+
entity: '',
|
|
211
|
+
accountColumn: '',
|
|
212
|
+
values: {}
|
|
213
|
+
}
|
|
214
|
+
]
|
|
192
215
|
}),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
children: field
|
|
196
|
-
}, field))
|
|
197
|
-
]
|
|
216
|
+
type: "button",
|
|
217
|
+
children: "Add a membership"
|
|
198
218
|
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
219
|
+
]
|
|
220
|
+
}),
|
|
221
|
+
(value.joins ?? []).length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
222
|
+
className: theme.field.hint,
|
|
223
|
+
children: "Nothing. An account with no role can sign in and do nothing — every screen it reaches refuses it. A role usually lives in its own table, joined to the account, which is what this adds."
|
|
224
|
+
}) : null,
|
|
225
|
+
(value.joins ?? []).map((join, joinIndex)=>{
|
|
226
|
+
const joinEntity = targets.find((target)=>target.entity === join.entity);
|
|
227
|
+
const pair = Object.entries(join.values)[0] ?? [
|
|
228
|
+
'',
|
|
229
|
+
''
|
|
230
|
+
];
|
|
231
|
+
const setJoin = (next)=>patch({
|
|
232
|
+
joins: (value.joins ?? []).with(joinIndex, {
|
|
233
|
+
...join,
|
|
234
|
+
...next
|
|
235
|
+
})
|
|
236
|
+
});
|
|
237
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
238
|
+
className: "grid grid-cols-1 gap-2 sm:grid-cols-[1fr_1fr_1fr_1fr_auto]",
|
|
207
239
|
children: [
|
|
208
|
-
/*#__PURE__*/
|
|
209
|
-
className: theme.field.
|
|
210
|
-
|
|
240
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
241
|
+
className: theme.field.input,
|
|
242
|
+
disabled: disabled,
|
|
243
|
+
onChange: (event)=>setJoin({
|
|
244
|
+
entity: event.target.value,
|
|
245
|
+
accountColumn: '',
|
|
246
|
+
values: {}
|
|
247
|
+
}),
|
|
248
|
+
value: join.entity,
|
|
249
|
+
children: [
|
|
250
|
+
/*#__PURE__*/ _jsx("option", {
|
|
251
|
+
value: "",
|
|
252
|
+
children: "Membership table…"
|
|
253
|
+
}),
|
|
254
|
+
targets.map((target)=>/*#__PURE__*/ _jsx("option", {
|
|
255
|
+
value: target.entity,
|
|
256
|
+
children: target.label
|
|
257
|
+
}, target.entity))
|
|
258
|
+
]
|
|
259
|
+
}),
|
|
260
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
261
|
+
className: theme.field.input,
|
|
262
|
+
disabled: disabled || !joinEntity,
|
|
263
|
+
onChange: (event)=>setJoin({
|
|
264
|
+
accountColumn: event.target.value
|
|
265
|
+
}),
|
|
266
|
+
value: join.accountColumn,
|
|
267
|
+
children: [
|
|
268
|
+
/*#__PURE__*/ _jsx("option", {
|
|
269
|
+
value: "",
|
|
270
|
+
children: "…account id column"
|
|
271
|
+
}),
|
|
272
|
+
(joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
273
|
+
value: field,
|
|
274
|
+
children: field
|
|
275
|
+
}, field))
|
|
276
|
+
]
|
|
277
|
+
}),
|
|
278
|
+
/*#__PURE__*/ _jsxs("select", {
|
|
279
|
+
className: theme.field.input,
|
|
280
|
+
disabled: disabled || !joinEntity,
|
|
281
|
+
onChange: (event)=>setJoin({
|
|
282
|
+
values: {
|
|
283
|
+
[event.target.value]: pair[1] ?? ''
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
value: pair[0],
|
|
287
|
+
children: [
|
|
288
|
+
/*#__PURE__*/ _jsx("option", {
|
|
289
|
+
value: "",
|
|
290
|
+
children: "…and this column"
|
|
291
|
+
}),
|
|
292
|
+
(joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
293
|
+
value: field,
|
|
294
|
+
children: field
|
|
295
|
+
}, field))
|
|
296
|
+
]
|
|
297
|
+
}),
|
|
298
|
+
/*#__PURE__*/ _jsx("input", {
|
|
299
|
+
className: theme.field.input,
|
|
300
|
+
disabled: disabled || !pair[0],
|
|
301
|
+
onChange: (event)=>setJoin({
|
|
302
|
+
values: {
|
|
303
|
+
[pair[0]]: event.target.value
|
|
304
|
+
}
|
|
305
|
+
}),
|
|
306
|
+
placeholder: "…set to this value",
|
|
307
|
+
value: String(pair[1] ?? '')
|
|
211
308
|
}),
|
|
212
309
|
/*#__PURE__*/ _jsx("button", {
|
|
213
310
|
className: theme.button.ghost,
|
|
214
311
|
disabled: disabled,
|
|
215
312
|
onClick: ()=>patch({
|
|
216
|
-
joins: [
|
|
217
|
-
...value.joins ?? [],
|
|
218
|
-
{
|
|
219
|
-
entity: '',
|
|
220
|
-
accountColumn: '',
|
|
221
|
-
values: {}
|
|
222
|
-
}
|
|
223
|
-
]
|
|
313
|
+
joins: (value.joins ?? []).toSpliced(joinIndex, 1)
|
|
224
314
|
}),
|
|
225
315
|
type: "button",
|
|
226
|
-
children: "
|
|
316
|
+
children: "Remove"
|
|
227
317
|
})
|
|
228
318
|
]
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
...next
|
|
244
|
-
})
|
|
245
|
-
});
|
|
246
|
-
return /*#__PURE__*/ _jsxs("div", {
|
|
247
|
-
className: "grid grid-cols-1 gap-2 sm:grid-cols-[1fr_1fr_1fr_1fr_auto]",
|
|
248
|
-
children: [
|
|
249
|
-
/*#__PURE__*/ _jsxs("select", {
|
|
250
|
-
className: theme.field.input,
|
|
251
|
-
disabled: disabled,
|
|
252
|
-
onChange: (event)=>setJoin({
|
|
253
|
-
entity: event.target.value,
|
|
254
|
-
accountColumn: '',
|
|
255
|
-
values: {}
|
|
256
|
-
}),
|
|
257
|
-
value: join.entity,
|
|
258
|
-
children: [
|
|
259
|
-
/*#__PURE__*/ _jsx("option", {
|
|
260
|
-
value: "",
|
|
261
|
-
children: "Membership table…"
|
|
262
|
-
}),
|
|
263
|
-
targets.map((target)=>/*#__PURE__*/ _jsx("option", {
|
|
264
|
-
value: target.entity,
|
|
265
|
-
children: target.label
|
|
266
|
-
}, target.entity))
|
|
267
|
-
]
|
|
268
|
-
}),
|
|
269
|
-
/*#__PURE__*/ _jsxs("select", {
|
|
270
|
-
className: theme.field.input,
|
|
271
|
-
disabled: disabled || !joinEntity,
|
|
272
|
-
onChange: (event)=>setJoin({
|
|
273
|
-
accountColumn: event.target.value
|
|
274
|
-
}),
|
|
275
|
-
value: join.accountColumn,
|
|
276
|
-
children: [
|
|
277
|
-
/*#__PURE__*/ _jsx("option", {
|
|
278
|
-
value: "",
|
|
279
|
-
children: "…account id column"
|
|
280
|
-
}),
|
|
281
|
-
(joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
282
|
-
value: field,
|
|
283
|
-
children: field
|
|
284
|
-
}, field))
|
|
285
|
-
]
|
|
286
|
-
}),
|
|
287
|
-
/*#__PURE__*/ _jsxs("select", {
|
|
288
|
-
className: theme.field.input,
|
|
289
|
-
disabled: disabled || !joinEntity,
|
|
290
|
-
onChange: (event)=>setJoin({
|
|
291
|
-
values: {
|
|
292
|
-
[event.target.value]: pair[1] ?? ''
|
|
293
|
-
}
|
|
294
|
-
}),
|
|
295
|
-
value: pair[0],
|
|
296
|
-
children: [
|
|
297
|
-
/*#__PURE__*/ _jsx("option", {
|
|
298
|
-
value: "",
|
|
299
|
-
children: "…and this column"
|
|
300
|
-
}),
|
|
301
|
-
(joinEntity?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
302
|
-
value: field,
|
|
303
|
-
children: field
|
|
304
|
-
}, field))
|
|
305
|
-
]
|
|
306
|
-
}),
|
|
307
|
-
/*#__PURE__*/ _jsx("input", {
|
|
308
|
-
className: theme.field.input,
|
|
309
|
-
disabled: disabled || !pair[0],
|
|
310
|
-
onChange: (event)=>setJoin({
|
|
311
|
-
values: {
|
|
312
|
-
[pair[0]]: event.target.value
|
|
313
|
-
}
|
|
314
|
-
}),
|
|
315
|
-
placeholder: "…set to this value",
|
|
316
|
-
value: String(pair[1] ?? '')
|
|
317
|
-
}),
|
|
318
|
-
/*#__PURE__*/ _jsx("button", {
|
|
319
|
-
className: theme.button.ghost,
|
|
320
|
-
disabled: disabled,
|
|
321
|
-
onClick: ()=>patch({
|
|
322
|
-
joins: (value.joins ?? []).toSpliced(joinIndex, 1)
|
|
323
|
-
}),
|
|
324
|
-
type: "button",
|
|
325
|
-
children: "Remove"
|
|
326
|
-
})
|
|
327
|
-
]
|
|
328
|
-
}, joinIndex);
|
|
329
|
-
})
|
|
330
|
-
]
|
|
331
|
-
}),
|
|
332
|
-
/*#__PURE__*/ _jsx(Notice, {
|
|
333
|
-
title: "One password, typed on the import screen",
|
|
334
|
-
tone: "info",
|
|
335
|
-
children: "Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second."
|
|
336
|
-
})
|
|
337
|
-
]
|
|
338
|
-
}) : /*#__PURE__*/ _jsx("p", {
|
|
339
|
-
className: theme.list.empty,
|
|
340
|
-
children: "Off. Records are imported without a login — useful for a directory, not for people who have to sign in."
|
|
341
|
-
})
|
|
342
|
-
]
|
|
319
|
+
}, joinIndex);
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
}),
|
|
323
|
+
/*#__PURE__*/ _jsx(Notice, {
|
|
324
|
+
title: "One password, typed on the import screen",
|
|
325
|
+
tone: "info",
|
|
326
|
+
children: "Every account this run creates is given the same opening password — asked for once, when the import is started, and stored nowhere. People change it themselves afterwards. A record whose value already has an account is linked to that one instead, so running this import again does not create a second."
|
|
327
|
+
})
|
|
328
|
+
]
|
|
329
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
330
|
+
className: theme.list.empty,
|
|
331
|
+
children: "Off. Records are imported without a login — useful for a directory, not for people who have to sign in."
|
|
332
|
+
})
|
|
343
333
|
});
|
|
344
334
|
}
|
|
@@ -10,7 +10,7 @@ import { MappingPasswordField } from './MappingPasswordField';
|
|
|
10
10
|
import { MappingScheduleFields } from './MappingScheduleFields';
|
|
11
11
|
import { MappingWriteModeFields } from './MappingWriteModeFields';
|
|
12
12
|
import { mappingDraftIssues } from './mappingDraft';
|
|
13
|
-
import { Field } from './Primitives';
|
|
13
|
+
import { Field, Section } from './Primitives';
|
|
14
14
|
import { ReferenceCheckRows } from './ReferenceCheckRows';
|
|
15
15
|
import { CardSkeleton } from './Skeletons';
|
|
16
16
|
/**
|
|
@@ -76,117 +76,127 @@ export function MappingEditor({ mapping, targets, targetsLoading, endpoints, fre
|
|
|
76
76
|
/*#__PURE__*/ _jsxs("div", {
|
|
77
77
|
className: theme.card.body,
|
|
78
78
|
children: [
|
|
79
|
-
/*#__PURE__*/
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
label: labels.mappings.readsFrom,
|
|
83
|
-
children: /*#__PURE__*/ _jsxs("select", {
|
|
84
|
-
className: theme.field.input,
|
|
85
|
-
disabled: busy || endpoints.length === 0,
|
|
86
|
-
id: `${uid}-endpoint`,
|
|
87
|
-
onChange: (event)=>patch({
|
|
88
|
-
endpointId: event.target.value
|
|
89
|
-
}),
|
|
90
|
-
value: draft.endpointId,
|
|
91
|
-
children: [
|
|
92
|
-
/*#__PURE__*/ _jsx("option", {
|
|
93
|
-
value: "",
|
|
94
|
-
children: labels.mappings.notSet
|
|
95
|
-
}),
|
|
96
|
-
endpoints.map((endpoint)=>/*#__PURE__*/ _jsx("option", {
|
|
97
|
-
value: endpoint.id,
|
|
98
|
-
children: endpointLabel(endpoint)
|
|
99
|
-
}, endpoint.id))
|
|
100
|
-
]
|
|
101
|
-
})
|
|
102
|
-
}),
|
|
103
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
104
|
-
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
79
|
+
/*#__PURE__*/ _jsxs(Section, {
|
|
80
|
+
purpose: "Which call this reads, what it is called, and which of your tables it writes into.",
|
|
81
|
+
title: "What this import is",
|
|
105
82
|
children: [
|
|
106
83
|
/*#__PURE__*/ _jsx(Field, {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
84
|
+
hint: endpoints.length === 0 ? labels.mappings.noEndpointsBody : readsFrom?.path ?? labels.mappings.readsFromHint,
|
|
85
|
+
htmlFor: `${uid}-endpoint`,
|
|
86
|
+
label: labels.mappings.readsFrom,
|
|
87
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
110
88
|
className: theme.field.input,
|
|
111
|
-
disabled: busy,
|
|
112
|
-
id: `${uid}-
|
|
89
|
+
disabled: busy || endpoints.length === 0,
|
|
90
|
+
id: `${uid}-endpoint`,
|
|
113
91
|
onChange: (event)=>patch({
|
|
114
|
-
|
|
92
|
+
endpointId: event.target.value
|
|
115
93
|
}),
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
value: draft.endpointId,
|
|
95
|
+
children: [
|
|
96
|
+
/*#__PURE__*/ _jsx("option", {
|
|
97
|
+
value: "",
|
|
98
|
+
children: labels.mappings.notSet
|
|
99
|
+
}),
|
|
100
|
+
endpoints.map((endpoint)=>/*#__PURE__*/ _jsx("option", {
|
|
101
|
+
value: endpoint.id,
|
|
102
|
+
children: endpointLabel(endpoint)
|
|
103
|
+
}, endpoint.id))
|
|
104
|
+
]
|
|
118
105
|
})
|
|
119
106
|
}),
|
|
107
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
108
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
109
|
+
children: [
|
|
110
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
111
|
+
htmlFor: `${uid}-name`,
|
|
112
|
+
label: labels.mappings.name,
|
|
113
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
114
|
+
className: theme.field.input,
|
|
115
|
+
disabled: busy,
|
|
116
|
+
id: `${uid}-name`,
|
|
117
|
+
onChange: (event)=>patch({
|
|
118
|
+
name: event.target.value
|
|
119
|
+
}),
|
|
120
|
+
placeholder: labels.mappings.namePlaceholder,
|
|
121
|
+
value: draft.name
|
|
122
|
+
})
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
125
|
+
hint: targets.length === 0 ? labels.mappings.targetNone : undefined,
|
|
126
|
+
htmlFor: `${uid}-target`,
|
|
127
|
+
label: labels.mappings.target,
|
|
128
|
+
children: /*#__PURE__*/ _jsxs("select", {
|
|
129
|
+
className: theme.field.input,
|
|
130
|
+
disabled: busy || targets.length === 0,
|
|
131
|
+
id: `${uid}-target`,
|
|
132
|
+
onChange: (event)=>chooseTarget(event.target.value),
|
|
133
|
+
value: draft.targetEntity,
|
|
134
|
+
children: [
|
|
135
|
+
/*#__PURE__*/ _jsx("option", {
|
|
136
|
+
value: "",
|
|
137
|
+
children: labels.mappings.notSet
|
|
138
|
+
}),
|
|
139
|
+
targets.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
140
|
+
value: option.entity,
|
|
141
|
+
children: option.label
|
|
142
|
+
}, option.entity))
|
|
143
|
+
]
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
]
|
|
147
|
+
}),
|
|
120
148
|
/*#__PURE__*/ _jsx(Field, {
|
|
121
|
-
hint:
|
|
122
|
-
htmlFor: `${uid}-
|
|
123
|
-
label: labels.mappings.
|
|
149
|
+
hint: targetFields.length === 0 ? labels.mappings.dedupHintNoTarget : labels.mappings.dedupHint,
|
|
150
|
+
htmlFor: `${uid}-dedup`,
|
|
151
|
+
label: labels.mappings.dedup,
|
|
124
152
|
children: /*#__PURE__*/ _jsxs("select", {
|
|
153
|
+
"aria-describedby": `${uid}-dedup-hint`,
|
|
125
154
|
className: theme.field.input,
|
|
126
|
-
disabled: busy ||
|
|
127
|
-
id: `${uid}-
|
|
128
|
-
onChange: (event)=>
|
|
129
|
-
|
|
155
|
+
disabled: busy || targetFields.length === 0,
|
|
156
|
+
id: `${uid}-dedup`,
|
|
157
|
+
onChange: (event)=>patch({
|
|
158
|
+
dedupTargetField: event.target.value || null
|
|
159
|
+
}),
|
|
160
|
+
value: draft.dedupTargetField ?? '',
|
|
130
161
|
children: [
|
|
131
162
|
/*#__PURE__*/ _jsx("option", {
|
|
132
163
|
value: "",
|
|
133
164
|
children: labels.mappings.notSet
|
|
134
165
|
}),
|
|
135
|
-
|
|
136
|
-
value:
|
|
137
|
-
children:
|
|
138
|
-
},
|
|
166
|
+
targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
167
|
+
value: field,
|
|
168
|
+
children: field
|
|
169
|
+
}, field))
|
|
139
170
|
]
|
|
140
171
|
})
|
|
141
172
|
})
|
|
142
173
|
]
|
|
143
174
|
}),
|
|
144
|
-
/*#__PURE__*/ _jsx(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
175
|
+
/*#__PURE__*/ _jsx(Section, {
|
|
176
|
+
purpose: "How a record already here is recognised, and what happens to the ones the source stops sending.",
|
|
177
|
+
title: "Matching and writing",
|
|
178
|
+
children: /*#__PURE__*/ _jsx(MappingWriteModeFields, {
|
|
179
|
+
dedupConfigured: (draft.dedupTargetField ?? '').trim() !== '',
|
|
180
|
+
disabled: busy,
|
|
181
|
+
idPrefix: uid,
|
|
182
|
+
missingRecordColumn: draft.missingRecordColumn ?? '',
|
|
183
|
+
missingRecordPolicy: draft.missingRecordPolicy,
|
|
184
|
+
missingRecordValue: draft.missingRecordValue ?? '',
|
|
185
|
+
onMissingRecordColumnChange: (value)=>patch({
|
|
186
|
+
missingRecordColumn: value
|
|
155
187
|
}),
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/*#__PURE__*/ _jsx("option", {
|
|
159
|
-
value: "",
|
|
160
|
-
children: labels.mappings.notSet
|
|
188
|
+
onMissingRecordPolicyChange: (policy)=>patch({
|
|
189
|
+
missingRecordPolicy: policy
|
|
161
190
|
}),
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
191
|
+
onMissingRecordValueChange: (value)=>patch({
|
|
192
|
+
missingRecordValue: value
|
|
193
|
+
}),
|
|
194
|
+
onWriteModeChange: (mode)=>patch({
|
|
195
|
+
writeMode: mode
|
|
196
|
+
}),
|
|
197
|
+
writeMode: draft.writeMode
|
|
167
198
|
})
|
|
168
199
|
}),
|
|
169
|
-
/*#__PURE__*/ _jsx(MappingWriteModeFields, {
|
|
170
|
-
dedupConfigured: (draft.dedupTargetField ?? '').trim() !== '',
|
|
171
|
-
disabled: busy,
|
|
172
|
-
idPrefix: uid,
|
|
173
|
-
missingRecordColumn: draft.missingRecordColumn ?? '',
|
|
174
|
-
missingRecordPolicy: draft.missingRecordPolicy,
|
|
175
|
-
missingRecordValue: draft.missingRecordValue ?? '',
|
|
176
|
-
onMissingRecordColumnChange: (value)=>patch({
|
|
177
|
-
missingRecordColumn: value
|
|
178
|
-
}),
|
|
179
|
-
onMissingRecordPolicyChange: (policy)=>patch({
|
|
180
|
-
missingRecordPolicy: policy
|
|
181
|
-
}),
|
|
182
|
-
onMissingRecordValueChange: (value)=>patch({
|
|
183
|
-
missingRecordValue: value
|
|
184
|
-
}),
|
|
185
|
-
onWriteModeChange: (mode)=>patch({
|
|
186
|
-
writeMode: mode
|
|
187
|
-
}),
|
|
188
|
-
writeMode: draft.writeMode
|
|
189
|
-
}),
|
|
190
200
|
/*#__PURE__*/ _jsx(FieldMappingRows, {
|
|
191
201
|
disabled: busy,
|
|
192
202
|
onChange: (rows)=>patch({
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { describeCron, parseCron } from '../../../../src/Shared/cron';
|
|
5
5
|
import { integrationsPageTheme } from '../theme';
|
|
6
|
-
import { Field, Notice } from './Primitives';
|
|
6
|
+
import { Field, Notice, Section } from './Primitives';
|
|
7
7
|
/**
|
|
8
8
|
* Running this import without anybody clicking anything.
|
|
9
9
|
*
|
|
@@ -51,100 +51,90 @@ export function MappingScheduleFields({ enabled, cron, timezone, needsPassword,
|
|
|
51
51
|
...next
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
|
-
return /*#__PURE__*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
return /*#__PURE__*/ _jsx(Section, {
|
|
55
|
+
action: /*#__PURE__*/ _jsxs("label", {
|
|
56
|
+
className: "flex items-center gap-2 text-sm",
|
|
57
|
+
children: [
|
|
58
|
+
/*#__PURE__*/ _jsx("input", {
|
|
59
|
+
checked: enabled,
|
|
60
|
+
disabled: disabled,
|
|
61
|
+
id: `${uid}-enabled`,
|
|
62
|
+
onChange: (event)=>patch({
|
|
63
|
+
enabled: event.target.checked
|
|
64
|
+
}),
|
|
65
|
+
type: "checkbox"
|
|
66
|
+
}),
|
|
67
|
+
/*#__PURE__*/ _jsx("span", {
|
|
68
|
+
className: theme.field.label,
|
|
69
|
+
children: "Enabled"
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
}),
|
|
73
|
+
purpose: "Every server checks it and exactly one takes the import — the same lock that stops two people running it at once.",
|
|
74
|
+
title: "Run this import on a schedule",
|
|
75
|
+
children: enabled ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
76
|
+
children: [
|
|
77
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
78
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
79
|
+
children: [
|
|
80
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
81
|
+
error: invalid && !parsed.ok ? parsed.error : null,
|
|
82
|
+
hint: cron.trim() === '' ? 'Five parts: minute hour day-of-month month day-of-week.' : describeCron(cron, timezone),
|
|
83
|
+
htmlFor: `${uid}-cron`,
|
|
84
|
+
label: "When",
|
|
85
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
86
|
+
className: theme.field.input,
|
|
69
87
|
disabled: disabled,
|
|
70
|
-
id: `${uid}-
|
|
88
|
+
id: `${uid}-cron`,
|
|
71
89
|
onChange: (event)=>patch({
|
|
72
|
-
|
|
90
|
+
cron: event.target.value
|
|
73
91
|
}),
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/*#__PURE__*/ _jsx("span", {
|
|
77
|
-
className: theme.field.label,
|
|
78
|
-
children: "Enabled"
|
|
92
|
+
placeholder: "0 2 * * *",
|
|
93
|
+
value: cron
|
|
79
94
|
})
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
88
|
-
children: [
|
|
89
|
-
/*#__PURE__*/ _jsx(Field, {
|
|
90
|
-
error: invalid && !parsed.ok ? parsed.error : null,
|
|
91
|
-
hint: cron.trim() === '' ? 'Five parts: minute hour day-of-month month day-of-week.' : describeCron(cron, timezone),
|
|
92
|
-
htmlFor: `${uid}-cron`,
|
|
93
|
-
label: "When",
|
|
94
|
-
children: /*#__PURE__*/ _jsx("input", {
|
|
95
|
-
className: theme.field.input,
|
|
96
|
-
disabled: disabled,
|
|
97
|
-
id: `${uid}-cron`,
|
|
98
|
-
onChange: (event)=>patch({
|
|
99
|
-
cron: event.target.value
|
|
100
|
-
}),
|
|
101
|
-
placeholder: "0 2 * * *",
|
|
102
|
-
value: cron
|
|
103
|
-
})
|
|
104
|
-
}),
|
|
105
|
-
/*#__PURE__*/ _jsx(Field, {
|
|
106
|
-
hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
|
|
107
|
-
htmlFor: `${uid}-timezone`,
|
|
108
|
-
label: "In this timezone",
|
|
109
|
-
children: /*#__PURE__*/ _jsx("input", {
|
|
110
|
-
className: theme.field.input,
|
|
111
|
-
disabled: disabled,
|
|
112
|
-
id: `${uid}-timezone`,
|
|
113
|
-
onChange: (event)=>patch({
|
|
114
|
-
timezone: event.target.value
|
|
115
|
-
}),
|
|
116
|
-
placeholder: "Europe/Istanbul",
|
|
117
|
-
value: timezone
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
]
|
|
121
|
-
}),
|
|
122
|
-
/*#__PURE__*/ _jsx("div", {
|
|
123
|
-
className: "flex flex-wrap gap-2",
|
|
124
|
-
children: PRESETS.map((preset)=>/*#__PURE__*/ _jsx("button", {
|
|
125
|
-
className: theme.button.ghost,
|
|
95
|
+
}),
|
|
96
|
+
/*#__PURE__*/ _jsx(Field, {
|
|
97
|
+
hint: "The company's own clock, so 02:00 stays 02:00 across a daylight-saving change.",
|
|
98
|
+
htmlFor: `${uid}-timezone`,
|
|
99
|
+
label: "In this timezone",
|
|
100
|
+
children: /*#__PURE__*/ _jsx("input", {
|
|
101
|
+
className: theme.field.input,
|
|
126
102
|
disabled: disabled,
|
|
127
|
-
|
|
128
|
-
|
|
103
|
+
id: `${uid}-timezone`,
|
|
104
|
+
onChange: (event)=>patch({
|
|
105
|
+
timezone: event.target.value
|
|
129
106
|
}),
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
107
|
+
placeholder: "Europe/Istanbul",
|
|
108
|
+
value: timezone
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
}),
|
|
113
|
+
/*#__PURE__*/ _jsx("div", {
|
|
114
|
+
className: "flex flex-wrap gap-2",
|
|
115
|
+
children: PRESETS.map((preset)=>/*#__PURE__*/ _jsx("button", {
|
|
116
|
+
className: theme.button.ghost,
|
|
117
|
+
disabled: disabled,
|
|
118
|
+
onClick: ()=>patch({
|
|
119
|
+
cron: preset.cron
|
|
120
|
+
}),
|
|
121
|
+
type: "button",
|
|
122
|
+
children: preset.label
|
|
123
|
+
}, preset.cron))
|
|
124
|
+
}),
|
|
125
|
+
needsPassword ? /*#__PURE__*/ _jsx(Notice, {
|
|
126
|
+
title: "This import cannot run on its own",
|
|
127
|
+
tone: "warning",
|
|
128
|
+
children: "It creates a login for every new record, and a login needs a password — which there is nobody to type at that hour. Either start this import by hand, or turn the login off and give people their accounts another way."
|
|
129
|
+
}) : null,
|
|
130
|
+
/*#__PURE__*/ _jsx("p", {
|
|
131
|
+
className: theme.field.hint,
|
|
132
|
+
children: "Every server running this app checks the schedule, and exactly one of them takes the import — the same lock that stops two people running it at once."
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
}) : /*#__PURE__*/ _jsx("p", {
|
|
136
|
+
className: theme.list.empty,
|
|
137
|
+
children: "Off. This import runs when somebody asks for it, and at no other time."
|
|
138
|
+
})
|
|
149
139
|
});
|
|
150
140
|
}
|
|
@@ -63,3 +63,20 @@ export type CodeBlockProps = {
|
|
|
63
63
|
* copy a line break into a field where it will silently break the request.
|
|
64
64
|
*/
|
|
65
65
|
export declare function CodeBlock({ children }: CodeBlockProps): import("react/jsx-runtime").JSX.Element;
|
|
66
|
+
export type SectionProps = {
|
|
67
|
+
title: string;
|
|
68
|
+
/** One line saying what this block is for. Absent when the title carries it. */
|
|
69
|
+
purpose?: string;
|
|
70
|
+
/** Sits on the title row — an "add" button, usually. */
|
|
71
|
+
action?: ReactNode;
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A titled block inside a long form.
|
|
76
|
+
*
|
|
77
|
+
* The mapping screen asks five separate questions and used to put them in one
|
|
78
|
+
* unbroken column of inputs and paragraphs. Named blocks let the eye skip the
|
|
79
|
+
* questions it does not need, which is the whole difference between a form and
|
|
80
|
+
* a wall.
|
|
81
|
+
*/
|
|
82
|
+
export declare function Section({ title, purpose, action, children }: SectionProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -92,3 +92,40 @@ const theme = integrationsPageTheme;
|
|
|
92
92
|
})
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* A titled block inside a long form.
|
|
97
|
+
*
|
|
98
|
+
* The mapping screen asks five separate questions and used to put them in one
|
|
99
|
+
* unbroken column of inputs and paragraphs. Named blocks let the eye skip the
|
|
100
|
+
* questions it does not need, which is the whole difference between a form and
|
|
101
|
+
* a wall.
|
|
102
|
+
*/ export function Section({ title, purpose, action, children }) {
|
|
103
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
104
|
+
className: theme.section.wrapper,
|
|
105
|
+
children: [
|
|
106
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
107
|
+
className: "mb-3 flex flex-wrap items-start justify-between gap-2",
|
|
108
|
+
children: [
|
|
109
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
110
|
+
className: "flex min-w-0 flex-col gap-0.5",
|
|
111
|
+
children: [
|
|
112
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
113
|
+
className: theme.section.title,
|
|
114
|
+
children: title
|
|
115
|
+
}),
|
|
116
|
+
purpose ? /*#__PURE__*/ _jsx("p", {
|
|
117
|
+
className: theme.section.purpose,
|
|
118
|
+
children: purpose
|
|
119
|
+
}) : null
|
|
120
|
+
]
|
|
121
|
+
}),
|
|
122
|
+
action
|
|
123
|
+
]
|
|
124
|
+
}),
|
|
125
|
+
/*#__PURE__*/ _jsx("div", {
|
|
126
|
+
className: theme.section.body,
|
|
127
|
+
children: children
|
|
128
|
+
})
|
|
129
|
+
]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
4
|
import { integrationsPageTheme } from '../theme';
|
|
5
|
-
import { Notice } from './Primitives';
|
|
5
|
+
import { Notice, Section } from './Primitives';
|
|
6
6
|
/**
|
|
7
7
|
* Columns whose value has to already exist somewhere else.
|
|
8
8
|
*
|
|
@@ -47,28 +47,20 @@ export function ReferenceCheckRows({ rows, targetFields, sourceFields = [], targ
|
|
|
47
47
|
...next
|
|
48
48
|
}));
|
|
49
49
|
};
|
|
50
|
-
return /*#__PURE__*/ _jsxs(
|
|
51
|
-
|
|
50
|
+
return /*#__PURE__*/ _jsxs(Section, {
|
|
51
|
+
action: /*#__PURE__*/ _jsx("button", {
|
|
52
|
+
className: theme.button.secondary,
|
|
53
|
+
disabled: disabled || targetFields.length === 0,
|
|
54
|
+
onClick: ()=>onChange([
|
|
55
|
+
...rows,
|
|
56
|
+
EMPTY
|
|
57
|
+
]),
|
|
58
|
+
type: "button",
|
|
59
|
+
children: "Add a reference"
|
|
60
|
+
}),
|
|
61
|
+
purpose: "A record whose department or location is not here yet points at nothing. Declare it and the preview says so before anything is written.",
|
|
62
|
+
title: "Values that must already exist",
|
|
52
63
|
children: [
|
|
53
|
-
/*#__PURE__*/ _jsxs("div", {
|
|
54
|
-
className: "flex flex-wrap items-center justify-between gap-2",
|
|
55
|
-
children: [
|
|
56
|
-
/*#__PURE__*/ _jsx("h3", {
|
|
57
|
-
className: theme.card.title,
|
|
58
|
-
children: "Values that must already exist"
|
|
59
|
-
}),
|
|
60
|
-
/*#__PURE__*/ _jsx("button", {
|
|
61
|
-
className: theme.button.secondary,
|
|
62
|
-
disabled: disabled || targetFields.length === 0,
|
|
63
|
-
onClick: ()=>onChange([
|
|
64
|
-
...rows,
|
|
65
|
-
EMPTY
|
|
66
|
-
]),
|
|
67
|
-
type: "button",
|
|
68
|
-
children: "Add a reference"
|
|
69
|
-
})
|
|
70
|
-
]
|
|
71
|
-
}),
|
|
72
64
|
/*#__PURE__*/ _jsx("datalist", {
|
|
73
65
|
id: fromListId,
|
|
74
66
|
children: [
|
|
@@ -66,6 +66,14 @@ export type IntegrationsPageTheme = {
|
|
|
66
66
|
hint: string;
|
|
67
67
|
error: string;
|
|
68
68
|
};
|
|
69
|
+
/** A titled block inside a long form, so it reads as parts rather than a column. */
|
|
70
|
+
section: {
|
|
71
|
+
wrapper: string;
|
|
72
|
+
header: string;
|
|
73
|
+
title: string;
|
|
74
|
+
purpose: string;
|
|
75
|
+
body: string;
|
|
76
|
+
};
|
|
69
77
|
button: {
|
|
70
78
|
primary: string;
|
|
71
79
|
secondary: string;
|
|
@@ -76,6 +76,21 @@ export const integrationsPageTheme = {
|
|
|
76
76
|
purpose: 'mt-1 text-slate-600 text-xs leading-relaxed dark:text-slate-400',
|
|
77
77
|
evidence: 'mt-1.5 font-semibold text-[11px] text-emerald-700 dark:text-emerald-400'
|
|
78
78
|
},
|
|
79
|
+
/**
|
|
80
|
+
* A form of twenty controls is not a form, it is a wall.
|
|
81
|
+
*
|
|
82
|
+
* The mapping screen asks five separate questions — what this reads, where it
|
|
83
|
+
* lands, what must already exist, whether it opens a login, when it runs — and
|
|
84
|
+
* they were one unbroken column of inputs and paragraphs. Sectioned, each
|
|
85
|
+
* question is a block with a name, and the eye can skip the ones it does not
|
|
86
|
+
* need.
|
|
87
|
+
*/ section: {
|
|
88
|
+
wrapper: 'rounded-2xl border border-slate-200/70 bg-slate-50/50 p-4 sm:p-5 dark:border-slate-800 dark:bg-slate-950/30',
|
|
89
|
+
header: 'mb-3 flex flex-col gap-0.5',
|
|
90
|
+
title: 'font-semibold text-slate-900 text-sm tracking-tight dark:text-slate-100',
|
|
91
|
+
purpose: 'text-slate-500 text-xs leading-relaxed dark:text-slate-400',
|
|
92
|
+
body: 'flex flex-col gap-3.5'
|
|
93
|
+
},
|
|
79
94
|
field: {
|
|
80
95
|
wrapper: 'flex flex-col gap-1.5',
|
|
81
96
|
label: `${LABEL} text-slate-500 dark:text-slate-400`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.842",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|