@ai-matrx/records-ui 0.81.0 → 0.83.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/CHANGELOG.md +56 -0
- package/dist/index.cjs +1179 -467
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -12
- package/dist/index.d.ts +184 -12
- package/dist/index.js +1255 -543
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -88,6 +88,7 @@ __export(src_exports, {
|
|
|
88
88
|
FORMULA_OP_LABEL: () => FORMULA_OP_LABEL,
|
|
89
89
|
FORMULA_OP_VALUES: () => FORMULA_OP_VALUES,
|
|
90
90
|
FORM_FLOWS: () => FORM_FLOWS,
|
|
91
|
+
FORM_NOT_HERE_LINE: () => FORM_NOT_HERE_LINE,
|
|
91
92
|
FROZEN_COLUMN_WIDTH: () => FROZEN_COLUMN_WIDTH,
|
|
92
93
|
FieldControl: () => FieldControl,
|
|
93
94
|
FieldEditor: () => FieldEditor,
|
|
@@ -135,6 +136,7 @@ __export(src_exports, {
|
|
|
135
136
|
PAGE_VIEW_LABEL: () => PAGE_VIEW_LABEL,
|
|
136
137
|
PARITY_LABEL: () => PARITY_LABEL,
|
|
137
138
|
PARITY_MADE_OF: () => PARITY_MADE_OF,
|
|
139
|
+
PORTAL_NOT_HERE_LINE: () => PORTAL_NOT_HERE_LINE,
|
|
138
140
|
PROPOSED_CHANGE_ACT_LABEL: () => PROPOSED_CHANGE_ACT_LABEL,
|
|
139
141
|
PROPOSED_CHANGE_ACT_VALUES: () => PROPOSED_CHANGE_ACT_VALUES,
|
|
140
142
|
Peek: () => Peek,
|
|
@@ -149,6 +151,7 @@ __export(src_exports, {
|
|
|
149
151
|
PublicViewPage: () => PublicViewPage,
|
|
150
152
|
ROLLUP_AGG_LABEL: () => ROLLUP_AGG_LABEL,
|
|
151
153
|
ROLLUP_AGG_VALUES: () => ROLLUP_AGG_VALUES,
|
|
154
|
+
RULE_NOT_HERE_LINE: () => RULE_NOT_HERE_LINE,
|
|
152
155
|
RecordChat: () => RecordChat,
|
|
153
156
|
RecordChip: () => RecordChip,
|
|
154
157
|
RecordForm: () => RecordForm,
|
|
@@ -163,6 +166,7 @@ __export(src_exports, {
|
|
|
163
166
|
SAVED_VIEWS_UNAVAILABLE: () => SAVED_VIEWS_UNAVAILABLE,
|
|
164
167
|
SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS: () => SAY_WHAT_YOU_ARE_WAITING_FOR_AFTER_MS,
|
|
165
168
|
SERIES_COLORS: () => SERIES_COLORS,
|
|
169
|
+
SHARE_NOT_YOURS_LINE: () => SHARE_NOT_YOURS_LINE,
|
|
166
170
|
SOMEBODY: () => SOMEBODY,
|
|
167
171
|
STAGE_RULE_ON_FAIL_LABEL: () => STAGE_RULE_ON_FAIL_LABEL,
|
|
168
172
|
STORE_ANSWERS_THESE: () => STORE_ANSWERS_THESE,
|
|
@@ -185,6 +189,8 @@ __export(src_exports, {
|
|
|
185
189
|
VIEW_LAYOUTS: () => VIEW_LAYOUTS,
|
|
186
190
|
VIEW_NOT_SAVED_YET: () => VIEW_NOT_SAVED_YET,
|
|
187
191
|
VIEW_TABLE: () => VIEW_TABLE,
|
|
192
|
+
VISIBILITY_LANES: () => VISIBILITY_LANES,
|
|
193
|
+
VISIBILITY_LANE_TITLE: () => VISIBILITY_LANE_TITLE,
|
|
188
194
|
ViewBar: () => ViewBar,
|
|
189
195
|
ViewSwitcher: () => ViewSwitcher,
|
|
190
196
|
WITHHELD_RECORD_LABEL: () => WITHHELD_RECORD_LABEL,
|
|
@@ -196,6 +202,7 @@ __export(src_exports, {
|
|
|
196
202
|
actorWords: () => actorWords,
|
|
197
203
|
addFields: () => addFields,
|
|
198
204
|
archivedRowLine: () => archivedRowLine,
|
|
205
|
+
asRefusal: () => asRefusal,
|
|
199
206
|
askableFields: () => askableFields,
|
|
200
207
|
blockFromSpec: () => blockFromSpec,
|
|
201
208
|
bodyForReading: () => bodyForReading,
|
|
@@ -239,6 +246,7 @@ __export(src_exports, {
|
|
|
239
246
|
isPlainFieldType: () => isPlainFieldType,
|
|
240
247
|
isRelationFieldType: () => isRelationFieldType,
|
|
241
248
|
isSignatureField: () => isSignatureField,
|
|
249
|
+
keptByTheApp: () => keptByTheApp,
|
|
242
250
|
keyFor: () => keyFor,
|
|
243
251
|
kindsWithNoChoice: () => kindsWithNoChoice,
|
|
244
252
|
laneFor: () => laneFor,
|
|
@@ -261,15 +269,20 @@ __export(src_exports, {
|
|
|
261
269
|
previewLine: () => previewLine,
|
|
262
270
|
previewWords: () => previewWords,
|
|
263
271
|
publiclyAnswerable: () => publiclyAnswerable,
|
|
272
|
+
railFromParam: () => railFromParam,
|
|
264
273
|
recordName: () => recordName,
|
|
265
274
|
recordNameIn: () => recordNameIn,
|
|
266
275
|
recordsDataSource: () => recordsDataSource,
|
|
276
|
+
refusal: () => refusal,
|
|
267
277
|
refusalForAPerson: () => refusalForAPerson,
|
|
278
|
+
refusalFromThrown: () => refusalFromThrown,
|
|
268
279
|
refusalLineForAPerson: () => refusalLineForAPerson,
|
|
280
|
+
refusalOr: () => refusalOr,
|
|
269
281
|
renderValue: () => renderValue,
|
|
270
282
|
revokeConsequence: () => revokeConsequence,
|
|
271
283
|
rowName: () => rowName,
|
|
272
284
|
rowNameIn: () => rowNameIn,
|
|
285
|
+
saveViewPatch: () => saveViewPatch,
|
|
273
286
|
scalarText: () => scalarText,
|
|
274
287
|
shareUnavailableReason: () => shareUnavailableReason,
|
|
275
288
|
specFromBlock: () => specFromBlock,
|
|
@@ -279,6 +292,7 @@ __export(src_exports, {
|
|
|
279
292
|
tableName: () => tableName,
|
|
280
293
|
tableRightsAt: () => tableRightsAt,
|
|
281
294
|
tokenFor: () => tokenFor,
|
|
295
|
+
unknownRailLine: () => unknownRailLine,
|
|
282
296
|
unknownViewLine: () => unknownViewLine,
|
|
283
297
|
useCanShare: () => useCanShare,
|
|
284
298
|
useEmbedHandshake: () => useEmbedHandshake,
|
|
@@ -298,6 +312,7 @@ __export(src_exports, {
|
|
|
298
312
|
viewDocument: () => viewDocument,
|
|
299
313
|
viewFromRecord: () => viewFromRecord,
|
|
300
314
|
viewPatchDocument: () => viewPatchDocument,
|
|
315
|
+
visibilityLaneFor: () => visibilityLaneFor,
|
|
301
316
|
whatIsMissing: () => whatIsMissing,
|
|
302
317
|
whatYouMayDo: () => whatYouMayDo,
|
|
303
318
|
whatYouMayDoWithTable: () => whatYouMayDoWithTable,
|
|
@@ -826,13 +841,13 @@ function ReferenceBuilder({
|
|
|
826
841
|
// src/PersonPicker.tsx
|
|
827
842
|
var import_react9 = require("react");
|
|
828
843
|
var import_react10 = require("@ai-matrx/records/react");
|
|
829
|
-
var
|
|
844
|
+
var import_design_system5 = require("@ai-matrx/design-system");
|
|
830
845
|
|
|
831
846
|
// src/editors.tsx
|
|
832
847
|
var import_react7 = require("react");
|
|
833
848
|
var import_records4 = require("@ai-matrx/records");
|
|
834
849
|
var import_react8 = require("@ai-matrx/records/react");
|
|
835
|
-
var
|
|
850
|
+
var import_design_system4 = require("@ai-matrx/design-system");
|
|
836
851
|
|
|
837
852
|
// src/parity.ts
|
|
838
853
|
var import_records3 = require("@ai-matrx/records");
|
|
@@ -934,8 +949,119 @@ function fieldTypeLabel(field) {
|
|
|
934
949
|
// src/RelationPicker.tsx
|
|
935
950
|
var import_react5 = require("react");
|
|
936
951
|
var import_react6 = require("@ai-matrx/records/react");
|
|
952
|
+
var import_design_system3 = require("@ai-matrx/design-system");
|
|
953
|
+
|
|
954
|
+
// src/Refusal.tsx
|
|
937
955
|
var import_design_system2 = require("@ai-matrx/design-system");
|
|
938
|
-
var import_jsx_runtime3 = (
|
|
956
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
957
|
+
function hintIsMachineIdentity(hint) {
|
|
958
|
+
return isMachineIdentity(hint);
|
|
959
|
+
}
|
|
960
|
+
function hintForAPerson(hint) {
|
|
961
|
+
const said = (hint ?? "").trim();
|
|
962
|
+
if (said === "") return null;
|
|
963
|
+
return isMachineIdentity(said) ? null : said;
|
|
964
|
+
}
|
|
965
|
+
function RefusalNotice({
|
|
966
|
+
error,
|
|
967
|
+
className,
|
|
968
|
+
actions,
|
|
969
|
+
onKeepEditing,
|
|
970
|
+
onDiscard
|
|
971
|
+
}) {
|
|
972
|
+
const plain = refusalForAPerson(error);
|
|
973
|
+
const doors = onKeepEditing || onDiscard ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-wrap items-center gap-1 pt-0.5", "data-refusal-doors": "", children: [
|
|
974
|
+
onKeepEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
975
|
+
"button",
|
|
976
|
+
{
|
|
977
|
+
type: "button",
|
|
978
|
+
"data-refusal-keep-editing": "",
|
|
979
|
+
className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
|
|
980
|
+
onClick: onKeepEditing,
|
|
981
|
+
children: "Keep editing"
|
|
982
|
+
}
|
|
983
|
+
) : null,
|
|
984
|
+
onDiscard ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
985
|
+
"button",
|
|
986
|
+
{
|
|
987
|
+
type: "button",
|
|
988
|
+
"data-refusal-discard": "",
|
|
989
|
+
className: "rounded border px-2 py-0.5 text-xs hover:bg-muted",
|
|
990
|
+
onClick: onDiscard,
|
|
991
|
+
children: "Discard"
|
|
992
|
+
}
|
|
993
|
+
) : null
|
|
994
|
+
] }) : null;
|
|
995
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
996
|
+
import_design_system2.Alert,
|
|
997
|
+
{
|
|
998
|
+
variant: "destructive",
|
|
999
|
+
role: "alert",
|
|
1000
|
+
"data-refusal-notice": "",
|
|
1001
|
+
className: (0, import_design_system2.cn)("text-xs", className),
|
|
1002
|
+
...plain.forEngineers ? { title: plain.forEngineers } : {},
|
|
1003
|
+
children: [
|
|
1004
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
|
|
1005
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.AlertDescription, { className: "space-y-1 text-xs", children: [
|
|
1006
|
+
plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: plain.sentence }),
|
|
1007
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "opacity-80", children: plain.remedy }),
|
|
1008
|
+
plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1009
|
+
"p",
|
|
1010
|
+
{
|
|
1011
|
+
className: "sr-only",
|
|
1012
|
+
"aria-hidden": "true",
|
|
1013
|
+
"data-for-engineers": "",
|
|
1014
|
+
...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
|
|
1015
|
+
children: plain.forEngineers
|
|
1016
|
+
}
|
|
1017
|
+
) : null,
|
|
1018
|
+
actions,
|
|
1019
|
+
doors
|
|
1020
|
+
] })
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
function RefusalLine({ error, className }) {
|
|
1026
|
+
const plain = refusalForAPerson(error);
|
|
1027
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1028
|
+
"p",
|
|
1029
|
+
{
|
|
1030
|
+
role: "alert",
|
|
1031
|
+
"data-refusal-line": "",
|
|
1032
|
+
className: (0, import_design_system2.cn)("text-xs text-destructive", className),
|
|
1033
|
+
title: plain.forEngineers || void 0,
|
|
1034
|
+
children: refusalLineForAPerson(error)
|
|
1035
|
+
}
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/refusals.ts
|
|
1040
|
+
function refusal(code, sentence2, remedy, diagnostic) {
|
|
1041
|
+
return {
|
|
1042
|
+
code,
|
|
1043
|
+
message: sentence2,
|
|
1044
|
+
...remedy ? { hint: remedy } : {},
|
|
1045
|
+
...diagnostic ? { diagnostic } : {}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function refusalFromThrown(thrown, sentence2, remedy, code = "internal") {
|
|
1049
|
+
const said = thrown instanceof Error ? thrown.message : String(thrown ?? "");
|
|
1050
|
+
return refusal(code, sentence2, remedy, said ? `Thrown: ${said}` : null);
|
|
1051
|
+
}
|
|
1052
|
+
function refusalOr(error, code, sentence2) {
|
|
1053
|
+
return error ?? refusal(code, sentence2);
|
|
1054
|
+
}
|
|
1055
|
+
function asRefusal(thrown, sentence2, remedy, code = "internal") {
|
|
1056
|
+
const t = thrown;
|
|
1057
|
+
if (t && typeof t === "object" && typeof t.code === "string" && typeof t.message === "string" && !(thrown instanceof Error)) {
|
|
1058
|
+
return t;
|
|
1059
|
+
}
|
|
1060
|
+
return refusalFromThrown(thrown, sentence2, remedy, code);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/RelationPicker.tsx
|
|
1064
|
+
var import_jsx_runtime4 = (
|
|
939
1065
|
// The Field's own name is already printed above this control by
|
|
940
1066
|
// `FieldLabel`, so the sentence does not say it a second time.
|
|
941
1067
|
require("react/jsx-runtime")
|
|
@@ -945,7 +1071,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
945
1071
|
const client = (0, import_react6.useOptionalRecordsClient)();
|
|
946
1072
|
const [rows, setRows] = (0, import_react5.useState)(null);
|
|
947
1073
|
const [words, setWords] = (0, import_react5.useState)({});
|
|
948
|
-
const [
|
|
1074
|
+
const [refusal2, setRefusal] = (0, import_react5.useState)(null);
|
|
949
1075
|
const [search, setSearch] = (0, import_react5.useState)("");
|
|
950
1076
|
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
951
1077
|
const [target, setTarget] = (0, import_react5.useState)(null);
|
|
@@ -965,7 +1091,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
965
1091
|
void client.list({ table_id: target2, limit: 200 }).then((result) => {
|
|
966
1092
|
if (cancelled) return;
|
|
967
1093
|
if (result.ok) setRows(result.data.rows);
|
|
968
|
-
else setRefusal(
|
|
1094
|
+
else setRefusal(result.error);
|
|
969
1095
|
});
|
|
970
1096
|
return () => {
|
|
971
1097
|
cancelled = true;
|
|
@@ -999,14 +1125,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
999
1125
|
void client.tableList().then((result) => {
|
|
1000
1126
|
if (cancelled) return;
|
|
1001
1127
|
if (!result.ok) {
|
|
1002
|
-
setTargetRefusal(
|
|
1128
|
+
setTargetRefusal(result.error);
|
|
1003
1129
|
return;
|
|
1004
1130
|
}
|
|
1005
1131
|
const found = result.data.find((t) => t.id === id2) ?? null;
|
|
1006
1132
|
if (found) setTarget(found);
|
|
1007
1133
|
else
|
|
1008
1134
|
setTargetRefusal(
|
|
1009
|
-
|
|
1135
|
+
refusal(
|
|
1136
|
+
"door",
|
|
1137
|
+
"That Table is not one you can open, so a new record cannot be added to it from here."
|
|
1138
|
+
)
|
|
1010
1139
|
);
|
|
1011
1140
|
});
|
|
1012
1141
|
return () => {
|
|
@@ -1014,10 +1143,10 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1014
1143
|
};
|
|
1015
1144
|
}, [client, open, allowCreate, target, targetRefusal, field.relation_target]);
|
|
1016
1145
|
if (!client) {
|
|
1017
|
-
return /* @__PURE__ */ (0,
|
|
1146
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { id, className: (0, import_design_system3.cn)("text-xs text-muted-foreground", className), children: NO_STORE_ON_THIS_SCREEN_REASON });
|
|
1018
1147
|
}
|
|
1019
1148
|
if (!field.relation_target) {
|
|
1020
|
-
return /* @__PURE__ */ (0,
|
|
1149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
1021
1150
|
fieldName(field),
|
|
1022
1151
|
" points at records, but it does not say which Table yet, so there is nothing to pick from. A table admin sets its target in the field editor."
|
|
1023
1152
|
] });
|
|
@@ -1041,13 +1170,17 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1041
1170
|
const tableId = field.relation_target;
|
|
1042
1171
|
if (!tableId || title === "" || busy) return;
|
|
1043
1172
|
if (!target) {
|
|
1044
|
-
setCreateRefusal("Still reading that Table. Try again in a moment.");
|
|
1173
|
+
setCreateRefusal(refusal("unreachable", "Still reading that Table.", "Try again in a moment."));
|
|
1045
1174
|
return;
|
|
1046
1175
|
}
|
|
1047
1176
|
const titleField = target.title_field;
|
|
1048
1177
|
if (!titleField) {
|
|
1049
1178
|
setCreateRefusal(
|
|
1050
|
-
|
|
1179
|
+
refusal(
|
|
1180
|
+
"not_supported",
|
|
1181
|
+
`${target.name} has no column that names a record yet, so a new one would have nothing to show here.`,
|
|
1182
|
+
"A table admin picks its title column in that table's settings."
|
|
1183
|
+
)
|
|
1051
1184
|
);
|
|
1052
1185
|
return;
|
|
1053
1186
|
}
|
|
@@ -1056,7 +1189,7 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1056
1189
|
const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
|
|
1057
1190
|
if (!written.ok) {
|
|
1058
1191
|
setBusy(false);
|
|
1059
|
-
setCreateRefusal(
|
|
1192
|
+
setCreateRefusal(written.error);
|
|
1060
1193
|
return;
|
|
1061
1194
|
}
|
|
1062
1195
|
const createdId = String(written.data);
|
|
@@ -1071,8 +1204,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1071
1204
|
const visible = (rows ?? []).filter(
|
|
1072
1205
|
(row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
|
|
1073
1206
|
);
|
|
1074
|
-
return /* @__PURE__ */ (0,
|
|
1075
|
-
picked.map((recordId) => /* @__PURE__ */ (0,
|
|
1207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: (0, import_design_system3.cn)("flex flex-wrap items-center gap-1", className), children: [
|
|
1208
|
+
picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1076
1209
|
RecordChip,
|
|
1077
1210
|
{
|
|
1078
1211
|
title: titleOf(recordId),
|
|
@@ -1080,11 +1213,11 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1080
1213
|
},
|
|
1081
1214
|
recordId
|
|
1082
1215
|
)),
|
|
1083
|
-
disabled ? null : /* @__PURE__ */ (0,
|
|
1084
|
-
/* @__PURE__ */ (0,
|
|
1085
|
-
/* @__PURE__ */ (0,
|
|
1086
|
-
/* @__PURE__ */ (0,
|
|
1087
|
-
|
|
1216
|
+
disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.Popover, { open, onOpenChange: setOpen, children: [
|
|
1217
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system3.Button, { id, type: "button", size: "sm", variant: "outline", "aria-label": `Pick for ${fieldName(field)}`, children: picked.length === 0 ? "Pick" : "Change" }) }),
|
|
1218
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
|
|
1219
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1220
|
+
import_design_system3.BasicInput,
|
|
1088
1221
|
{
|
|
1089
1222
|
value: search,
|
|
1090
1223
|
onChange: (e) => setSearch(e.target.value),
|
|
@@ -1092,34 +1225,34 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1092
1225
|
className: "mb-1 h-7 text-xs"
|
|
1093
1226
|
}
|
|
1094
1227
|
),
|
|
1095
|
-
|
|
1096
|
-
!rows && !
|
|
1097
|
-
full ? /* @__PURE__ */ (0,
|
|
1228
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefusalLine, { error: refusal2, className: "p-2" }) : null,
|
|
1229
|
+
!rows && !refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
|
|
1230
|
+
full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
|
|
1098
1231
|
fieldName(field),
|
|
1099
1232
|
" can point at ",
|
|
1100
1233
|
field.relation_max,
|
|
1101
1234
|
" at most. Remove one to add another."
|
|
1102
1235
|
] }) : null,
|
|
1103
|
-
/* @__PURE__ */ (0,
|
|
1104
|
-
/* @__PURE__ */ (0,
|
|
1236
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_design_system3.ScrollArea, { className: "max-h-56", children: [
|
|
1237
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { children: visible.map((row) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1105
1238
|
"button",
|
|
1106
1239
|
{
|
|
1107
1240
|
type: "button",
|
|
1108
1241
|
onClick: () => toggle(row.id),
|
|
1109
1242
|
title: titleOf(row.id),
|
|
1110
|
-
className: (0,
|
|
1243
|
+
className: (0, import_design_system3.cn)(
|
|
1111
1244
|
"w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
|
|
1112
1245
|
picked.includes(row.id) ? "bg-accent text-accent-foreground" : ""
|
|
1113
1246
|
),
|
|
1114
1247
|
children: titleOf(row.id)
|
|
1115
1248
|
}
|
|
1116
1249
|
) }, row.id)) }),
|
|
1117
|
-
rows && visible.length === 0 ? /* @__PURE__ */ (0,
|
|
1250
|
+
rows && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
|
|
1118
1251
|
] }),
|
|
1119
|
-
allowCreate && !full ? /* @__PURE__ */ (0,
|
|
1120
|
-
targetRefusal ? /* @__PURE__ */ (0,
|
|
1121
|
-
/* @__PURE__ */ (0,
|
|
1122
|
-
|
|
1252
|
+
allowCreate && !full ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mt-1 border-t pt-1", children: [
|
|
1253
|
+
targetRefusal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1 p-1", children: [
|
|
1254
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1255
|
+
import_design_system3.BasicInput,
|
|
1123
1256
|
{
|
|
1124
1257
|
autoFocus: true,
|
|
1125
1258
|
value: newTitle,
|
|
@@ -1135,8 +1268,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1135
1268
|
"aria-label": `Name the new ${targetWord}`
|
|
1136
1269
|
}
|
|
1137
1270
|
),
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1139
|
-
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1272
|
+
import_design_system3.Button,
|
|
1140
1273
|
{
|
|
1141
1274
|
type: "button",
|
|
1142
1275
|
size: "sm",
|
|
@@ -1146,8 +1279,8 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1146
1279
|
children: busy ? "Creating\u2026" : "Create"
|
|
1147
1280
|
}
|
|
1148
1281
|
)
|
|
1149
|
-
] }) : /* @__PURE__ */ (0,
|
|
1150
|
-
|
|
1282
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1283
|
+
import_design_system3.Button,
|
|
1151
1284
|
{
|
|
1152
1285
|
type: "button",
|
|
1153
1286
|
size: "sm",
|
|
@@ -1162,10 +1295,25 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1162
1295
|
setCreating(true);
|
|
1163
1296
|
}
|
|
1164
1297
|
},
|
|
1165
|
-
children: /* @__PURE__ */ (0,
|
|
1298
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "block w-full truncate text-left", children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}` })
|
|
1166
1299
|
}
|
|
1167
1300
|
),
|
|
1168
|
-
createRefusal ?
|
|
1301
|
+
createRefusal ? (
|
|
1302
|
+
// The typed title is still in the search box: the notice owns the
|
|
1303
|
+
// two doors out (lane REFUSAL-SWEEP).
|
|
1304
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1305
|
+
RefusalNotice,
|
|
1306
|
+
{
|
|
1307
|
+
error: createRefusal,
|
|
1308
|
+
className: "mt-1 max-w-72",
|
|
1309
|
+
onKeepEditing: () => setCreateRefusal(null),
|
|
1310
|
+
onDiscard: () => {
|
|
1311
|
+
setCreateRefusal(null);
|
|
1312
|
+
setSearch("");
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
)
|
|
1316
|
+
) : null
|
|
1169
1317
|
] }) : null
|
|
1170
1318
|
] })
|
|
1171
1319
|
] })
|
|
@@ -1173,12 +1321,12 @@ function RelationPicker({ field, value, onChange, disabled, id, className, allow
|
|
|
1173
1321
|
}
|
|
1174
1322
|
|
|
1175
1323
|
// src/editors.tsx
|
|
1176
|
-
var
|
|
1324
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1177
1325
|
function FieldLabel({ field, htmlFor, children }) {
|
|
1178
|
-
return /* @__PURE__ */ (0,
|
|
1179
|
-
/* @__PURE__ */ (0,
|
|
1180
|
-
field.required ? /* @__PURE__ */ (0,
|
|
1181
|
-
field.unit ? /* @__PURE__ */ (0,
|
|
1326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-baseline gap-1.5", children: [
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.Label, { htmlFor, className: "text-xs font-medium", children: fieldName(field) }),
|
|
1328
|
+
field.required ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-destructive", children: "required" }) : null,
|
|
1329
|
+
field.unit ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: field.unit }) : null,
|
|
1182
1330
|
children
|
|
1183
1331
|
] });
|
|
1184
1332
|
}
|
|
@@ -1189,18 +1337,18 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1189
1337
|
case "formula":
|
|
1190
1338
|
case "lookup":
|
|
1191
1339
|
case "rollup":
|
|
1192
|
-
return /* @__PURE__ */ (0,
|
|
1340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { id: inputId, className: "rounded border border-dashed px-2 py-1.5 text-xs text-muted-foreground", children: [
|
|
1193
1341
|
value === null || value === void 0 ? "\u2014" : String(value),
|
|
1194
|
-
/* @__PURE__ */ (0,
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "ml-2 italic", children: [
|
|
1195
1343
|
fieldName(field),
|
|
1196
1344
|
" is worked out by the system, so it cannot be typed in"
|
|
1197
1345
|
] })
|
|
1198
1346
|
] });
|
|
1199
1347
|
case "json":
|
|
1200
|
-
return /* @__PURE__ */ (0,
|
|
1348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(JsonControl, { field, value, onChange, disabled, id: inputId });
|
|
1201
1349
|
case "long_text":
|
|
1202
|
-
return /* @__PURE__ */ (0,
|
|
1203
|
-
|
|
1350
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1351
|
+
import_design_system4.BasicTextarea,
|
|
1204
1352
|
{
|
|
1205
1353
|
id: inputId,
|
|
1206
1354
|
rows: 3,
|
|
@@ -1212,8 +1360,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1212
1360
|
case "number":
|
|
1213
1361
|
case "currency":
|
|
1214
1362
|
case "percent":
|
|
1215
|
-
return /* @__PURE__ */ (0,
|
|
1216
|
-
|
|
1363
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1364
|
+
import_design_system4.BasicInput,
|
|
1217
1365
|
{
|
|
1218
1366
|
id: inputId,
|
|
1219
1367
|
type: "number",
|
|
@@ -1225,8 +1373,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1225
1373
|
}
|
|
1226
1374
|
);
|
|
1227
1375
|
case "datetime":
|
|
1228
|
-
return /* @__PURE__ */ (0,
|
|
1229
|
-
|
|
1376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1377
|
+
import_design_system4.BasicInput,
|
|
1230
1378
|
{
|
|
1231
1379
|
id: inputId,
|
|
1232
1380
|
type: field.format === "datetime" ? "datetime-local" : "date",
|
|
@@ -1238,8 +1386,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1238
1386
|
case "email":
|
|
1239
1387
|
case "phone":
|
|
1240
1388
|
case "url":
|
|
1241
|
-
return /* @__PURE__ */ (0,
|
|
1242
|
-
|
|
1389
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1390
|
+
import_design_system4.BasicInput,
|
|
1243
1391
|
{
|
|
1244
1392
|
id: inputId,
|
|
1245
1393
|
type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
|
|
@@ -1249,19 +1397,19 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1249
1397
|
}
|
|
1250
1398
|
);
|
|
1251
1399
|
case "checkbox":
|
|
1252
|
-
return /* @__PURE__ */ (0,
|
|
1400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckboxControl, { field, value, onChange, disabled, id: inputId });
|
|
1253
1401
|
case "select":
|
|
1254
1402
|
case "multi_select":
|
|
1255
|
-
return /* @__PURE__ */ (0,
|
|
1403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(OptionControl, { field, value, onChange, disabled, id: inputId });
|
|
1256
1404
|
case "member":
|
|
1257
|
-
return /* @__PURE__ */ (0,
|
|
1405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PersonPicker, { field, value, onChange, disabled, id: inputId });
|
|
1258
1406
|
case "relation":
|
|
1259
1407
|
case "attachment":
|
|
1260
|
-
return /* @__PURE__ */ (0,
|
|
1408
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RelationPicker, { field, value, onChange, disabled, id: inputId });
|
|
1261
1409
|
default:
|
|
1262
1410
|
if (typeof value === "boolean" || field.config?.["kind"] === "boolean") {
|
|
1263
|
-
return /* @__PURE__ */ (0,
|
|
1264
|
-
|
|
1411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1412
|
+
import_design_system4.Checkbox,
|
|
1265
1413
|
{
|
|
1266
1414
|
id: inputId,
|
|
1267
1415
|
checked: value === true,
|
|
@@ -1270,8 +1418,8 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1270
1418
|
}
|
|
1271
1419
|
);
|
|
1272
1420
|
}
|
|
1273
|
-
return /* @__PURE__ */ (0,
|
|
1274
|
-
|
|
1421
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1422
|
+
import_design_system4.BasicInput,
|
|
1275
1423
|
{
|
|
1276
1424
|
id: inputId,
|
|
1277
1425
|
value: value === null || value === void 0 ? "" : String(value),
|
|
@@ -1283,9 +1431,9 @@ function FieldControl({ field, value, onChange, disabled, id }) {
|
|
|
1283
1431
|
}
|
|
1284
1432
|
function CheckboxControl({ field, value, onChange, disabled, id }) {
|
|
1285
1433
|
const unanswered = value === null || value === void 0;
|
|
1286
|
-
return /* @__PURE__ */ (0,
|
|
1287
|
-
/* @__PURE__ */ (0,
|
|
1288
|
-
|
|
1434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1435
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1436
|
+
import_design_system4.Checkbox,
|
|
1289
1437
|
{
|
|
1290
1438
|
id,
|
|
1291
1439
|
checked: value === true,
|
|
@@ -1294,10 +1442,10 @@ function CheckboxControl({ field, value, onChange, disabled, id }) {
|
|
|
1294
1442
|
onCheckedChange: (next) => onChange(next === true)
|
|
1295
1443
|
}
|
|
1296
1444
|
),
|
|
1297
|
-
unanswered ? /* @__PURE__ */ (0,
|
|
1298
|
-
/* @__PURE__ */ (0,
|
|
1299
|
-
field.required ? null : /* @__PURE__ */ (0,
|
|
1300
|
-
|
|
1445
|
+
unanswered ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs italic text-muted-foreground", children: "not answered yet" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1446
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs text-muted-foreground", children: value === true ? "Yes" : "No" }),
|
|
1447
|
+
field.required ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1448
|
+
import_design_system4.Button,
|
|
1301
1449
|
{
|
|
1302
1450
|
size: "sm",
|
|
1303
1451
|
variant: "ghost",
|
|
@@ -1314,9 +1462,9 @@ function JsonControl({ field, value, onChange, disabled, id }) {
|
|
|
1314
1462
|
const initial = value === null || value === void 0 ? "" : typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
1315
1463
|
const [text, setText] = (0, import_react7.useState)(initial);
|
|
1316
1464
|
const [broken, setBroken] = (0, import_react7.useState)(null);
|
|
1317
|
-
return /* @__PURE__ */ (0,
|
|
1318
|
-
/* @__PURE__ */ (0,
|
|
1319
|
-
|
|
1465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1467
|
+
import_design_system4.BasicTextarea,
|
|
1320
1468
|
{
|
|
1321
1469
|
id,
|
|
1322
1470
|
rows: 4,
|
|
@@ -1335,38 +1483,54 @@ function JsonControl({ field, value, onChange, disabled, id }) {
|
|
|
1335
1483
|
onChange(JSON.parse(next));
|
|
1336
1484
|
setBroken(null);
|
|
1337
1485
|
} catch (thrown) {
|
|
1338
|
-
setBroken(
|
|
1486
|
+
setBroken(
|
|
1487
|
+
refusalFromThrown(
|
|
1488
|
+
thrown,
|
|
1489
|
+
`${fieldName(field)} is not readable as structured data yet.`,
|
|
1490
|
+
"Fix the text so it reads as structured data, or discard it to put back what was there.",
|
|
1491
|
+
"invalid_argument"
|
|
1492
|
+
)
|
|
1493
|
+
);
|
|
1339
1494
|
}
|
|
1340
1495
|
}
|
|
1341
1496
|
}
|
|
1342
1497
|
),
|
|
1343
|
-
broken ?
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1498
|
+
broken ? (
|
|
1499
|
+
// The typed text is still in the box: the notice owns the two doors out.
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1501
|
+
RefusalNotice,
|
|
1502
|
+
{
|
|
1503
|
+
error: broken,
|
|
1504
|
+
onKeepEditing: () => id ? document.getElementById(id)?.focus() : void 0,
|
|
1505
|
+
onDiscard: () => {
|
|
1506
|
+
setText(initial);
|
|
1507
|
+
setBroken(null);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
) : null
|
|
1348
1512
|
] });
|
|
1349
1513
|
}
|
|
1350
1514
|
function OptionControl({ field, value, onChange, disabled, id }) {
|
|
1351
1515
|
const client = (0, import_react8.useOptionalRecordsClient)();
|
|
1352
1516
|
const [options, setOptions] = (0, import_react7.useState)(null);
|
|
1353
|
-
const [
|
|
1517
|
+
const [refusal2, setRefusal] = (0, import_react7.useState)(null);
|
|
1354
1518
|
(0, import_react7.useEffect)(() => {
|
|
1355
1519
|
if (!client) return;
|
|
1356
1520
|
let cancelled = false;
|
|
1357
1521
|
void client.fieldOptions({ field_id: field.id }).then((result) => {
|
|
1358
1522
|
if (cancelled) return;
|
|
1359
1523
|
if (result.ok) setOptions(result.data ?? []);
|
|
1360
|
-
else setRefusal(
|
|
1524
|
+
else setRefusal(result.error);
|
|
1361
1525
|
});
|
|
1362
1526
|
return () => {
|
|
1363
1527
|
cancelled = true;
|
|
1364
1528
|
};
|
|
1365
1529
|
}, [client, field.id]);
|
|
1366
1530
|
if (!client) {
|
|
1367
|
-
return /* @__PURE__ */ (0,
|
|
1368
|
-
/* @__PURE__ */ (0,
|
|
1369
|
-
|
|
1531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
1532
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1533
|
+
import_design_system4.BasicInput,
|
|
1370
1534
|
{
|
|
1371
1535
|
id,
|
|
1372
1536
|
value: value === null || value === void 0 ? "" : String(value),
|
|
@@ -1374,20 +1538,21 @@ function OptionControl({ field, value, onChange, disabled, id }) {
|
|
|
1374
1538
|
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
1375
1539
|
}
|
|
1376
1540
|
),
|
|
1377
|
-
/* @__PURE__ */ (0,
|
|
1541
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-xs text-muted-foreground", children: "This is normally picked from a list. A page answered without an account cannot read that list, so type your answer and whoever receives it will match it up." })
|
|
1378
1542
|
] });
|
|
1379
1543
|
}
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
1544
|
+
if (refusal2)
|
|
1545
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { id, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: refusal2 }) });
|
|
1546
|
+
if (!options) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id, className: "text-xs text-muted-foreground", children: "Reading this field's choices\u2026" });
|
|
1382
1547
|
if (options.length === 0) {
|
|
1383
|
-
return /* @__PURE__ */ (0,
|
|
1548
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id, className: "text-xs text-muted-foreground", children: field.options_table_id ? `${fieldName(field)} takes its choices from a Table that has no records yet \u2014 add the choices there and they appear here.` : `${fieldName(field)} does not name the Table its choices come from yet, so there is nothing to choose. A table admin sets it in the field editor.` });
|
|
1384
1549
|
}
|
|
1385
1550
|
const titleOf = (option) => recordName(option.data, null, "Unnamed choice");
|
|
1386
1551
|
if (field.multi) {
|
|
1387
1552
|
const chosen = options.filter((option) => (0, import_records4.choiceValuesOf)(value).some((v) => (0, import_records4.isTheChosen)(option, v)));
|
|
1388
1553
|
const picked = new Set(chosen.map((option) => (0, import_records4.optionKey)(option)));
|
|
1389
|
-
return /* @__PURE__ */ (0,
|
|
1390
|
-
|
|
1554
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { id, className: "flex flex-wrap gap-1", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1555
|
+
import_design_system4.Button,
|
|
1391
1556
|
{
|
|
1392
1557
|
type: "button",
|
|
1393
1558
|
size: "sm",
|
|
@@ -1405,15 +1570,15 @@ function OptionControl({ field, value, onChange, disabled, id }) {
|
|
|
1405
1570
|
)) });
|
|
1406
1571
|
}
|
|
1407
1572
|
const current = options.find((option) => (0, import_records4.isTheChosen)(option, value));
|
|
1408
|
-
return /* @__PURE__ */ (0,
|
|
1409
|
-
|
|
1573
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1574
|
+
import_design_system4.Select,
|
|
1410
1575
|
{
|
|
1411
1576
|
value: current ? (0, import_records4.optionKey)(current) : "",
|
|
1412
1577
|
disabled: disabled ?? false,
|
|
1413
1578
|
onValueChange: (next) => onChange(next === "" ? null : next),
|
|
1414
1579
|
children: [
|
|
1415
|
-
/* @__PURE__ */ (0,
|
|
1416
|
-
/* @__PURE__ */ (0,
|
|
1580
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectTrigger, { id, size: "sm", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectValue, { placeholder: "Choose" }) }),
|
|
1581
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.SelectItem, { value: (0, import_records4.optionKey)(option), children: titleOf(option) }, option.id)) })
|
|
1417
1582
|
]
|
|
1418
1583
|
}
|
|
1419
1584
|
);
|
|
@@ -1429,15 +1594,15 @@ function RecordChip({
|
|
|
1429
1594
|
// name. So it caps at its container and ends in an ellipsis with the whole name in
|
|
1430
1595
|
// its tooltip, rather than being cut mid-word by whatever box it landed in
|
|
1431
1596
|
// (lane FIX-14, 2026-09-22).
|
|
1432
|
-
/* @__PURE__ */ (0,
|
|
1433
|
-
/* @__PURE__ */ (0,
|
|
1434
|
-
onRemove ? /* @__PURE__ */ (0,
|
|
1597
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_design_system4.Badge, { variant: "secondary", className: (0, import_design_system4.cn)("max-w-full gap-1 text-[11px]", className), title, children: [
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 truncate", children: title }),
|
|
1599
|
+
onRemove ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", onClick: onRemove, "aria-label": `Remove ${title}`, className: "shrink-0 opacity-60 hover:opacity-100", children: "\xD7" }) : null
|
|
1435
1600
|
] })
|
|
1436
1601
|
);
|
|
1437
1602
|
}
|
|
1438
1603
|
|
|
1439
1604
|
// src/PersonPicker.tsx
|
|
1440
|
-
var
|
|
1605
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1441
1606
|
function personFromRow(row) {
|
|
1442
1607
|
const data = row.document ?? {};
|
|
1443
1608
|
const userId = data["user_id"];
|
|
@@ -1490,7 +1655,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1490
1655
|
]);
|
|
1491
1656
|
setMembers(roster);
|
|
1492
1657
|
if (records && !records.ok) {
|
|
1493
|
-
setError(
|
|
1658
|
+
setError(records.error);
|
|
1494
1659
|
return;
|
|
1495
1660
|
}
|
|
1496
1661
|
setPeople(records ? records.data.rows.map(personFromRow).filter((p) => p.userId !== null) : []);
|
|
@@ -1511,10 +1676,10 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1511
1676
|
};
|
|
1512
1677
|
}, [client, personTable, picked.length, people.length]);
|
|
1513
1678
|
if (!membersPort || !client) {
|
|
1514
|
-
return /* @__PURE__ */ (0,
|
|
1679
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: (0, import_design_system5.cn)("text-xs text-muted-foreground", className), children: NO_MEMBERS_REASON });
|
|
1515
1680
|
}
|
|
1516
1681
|
if (!personTable) {
|
|
1517
|
-
return /* @__PURE__ */ (0,
|
|
1682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: (0, import_design_system5.cn)("text-xs text-muted-foreground", className), children: [
|
|
1518
1683
|
fieldName(field),
|
|
1519
1684
|
" is a person field, but it does not say which Table the people are records of yet, so there is nothing to pick from. A table admin sets its target in the field editor."
|
|
1520
1685
|
] });
|
|
@@ -1532,7 +1697,7 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1532
1697
|
const resolved = await personRecordForMember(client, personTable, people, member);
|
|
1533
1698
|
setBusy(false);
|
|
1534
1699
|
if (!resolved.ok) {
|
|
1535
|
-
setError(
|
|
1700
|
+
setError(resolved.error);
|
|
1536
1701
|
return;
|
|
1537
1702
|
}
|
|
1538
1703
|
if (!people.some((p) => p.id === resolved.data)) {
|
|
@@ -1558,8 +1723,8 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1558
1723
|
const pickedUserIds = new Set(
|
|
1559
1724
|
picked.map((recordId) => people.find((p) => p.id === recordId)?.userId).filter(Boolean)
|
|
1560
1725
|
);
|
|
1561
|
-
return /* @__PURE__ */ (0,
|
|
1562
|
-
picked.map((recordId) => /* @__PURE__ */ (0,
|
|
1726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: (0, import_design_system5.cn)("flex flex-wrap items-center gap-1", className), children: [
|
|
1727
|
+
picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1563
1728
|
RecordChip,
|
|
1564
1729
|
{
|
|
1565
1730
|
title: nameOfRecord(recordId),
|
|
@@ -1567,9 +1732,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1567
1732
|
},
|
|
1568
1733
|
recordId
|
|
1569
1734
|
)),
|
|
1570
|
-
disabled ? null : /* @__PURE__ */ (0,
|
|
1571
|
-
/* @__PURE__ */ (0,
|
|
1572
|
-
|
|
1735
|
+
disabled ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.Popover, { open, onOpenChange: setOpen, children: [
|
|
1736
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1737
|
+
import_design_system5.Button,
|
|
1573
1738
|
{
|
|
1574
1739
|
id,
|
|
1575
1740
|
type: "button",
|
|
@@ -1579,9 +1744,9 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1579
1744
|
children: picked.length === 0 ? "Pick a person" : "Change"
|
|
1580
1745
|
}
|
|
1581
1746
|
) }),
|
|
1582
|
-
/* @__PURE__ */ (0,
|
|
1583
|
-
/* @__PURE__ */ (0,
|
|
1584
|
-
|
|
1747
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.PopoverContent, { sizing: "content", className: "p-1", align: "start", children: [
|
|
1748
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1749
|
+
import_design_system5.BasicInput,
|
|
1585
1750
|
{
|
|
1586
1751
|
value: search,
|
|
1587
1752
|
onChange: (e) => setSearch(e.target.value),
|
|
@@ -1589,29 +1754,29 @@ function PersonPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
1589
1754
|
className: "mb-1 h-7 text-xs"
|
|
1590
1755
|
}
|
|
1591
1756
|
),
|
|
1592
|
-
error ? /* @__PURE__ */ (0,
|
|
1593
|
-
members === null ? /* @__PURE__ */ (0,
|
|
1594
|
-
/* @__PURE__ */ (0,
|
|
1595
|
-
/* @__PURE__ */ (0,
|
|
1757
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RefusalLine, { error, className: "p-2" }) : null,
|
|
1758
|
+
members === null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading the members\u2026" }) : null,
|
|
1759
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.ScrollArea, { className: "max-h-56", children: [
|
|
1760
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: visible.map((member) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1596
1761
|
"button",
|
|
1597
1762
|
{
|
|
1598
1763
|
type: "button",
|
|
1599
1764
|
disabled: busy,
|
|
1600
1765
|
onClick: () => void pick(member),
|
|
1601
|
-
className: (0,
|
|
1766
|
+
className: (0, import_design_system5.cn)(
|
|
1602
1767
|
"w-full truncate rounded px-2 py-1 text-left text-xs hover:bg-muted",
|
|
1603
1768
|
pickedUserIds.has(member.userId) ? "bg-accent text-accent-foreground" : ""
|
|
1604
1769
|
),
|
|
1605
1770
|
children: [
|
|
1606
|
-
/* @__PURE__ */ (0,
|
|
1607
|
-
member.email && member.email !== memberName(member) ? /* @__PURE__ */ (0,
|
|
1771
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: memberName(member) }),
|
|
1772
|
+
member.email && member.email !== memberName(member) ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "ml-1.5 opacity-60", children: [
|
|
1608
1773
|
"\xB7 ",
|
|
1609
1774
|
member.email
|
|
1610
1775
|
] }) : null
|
|
1611
1776
|
]
|
|
1612
1777
|
}
|
|
1613
1778
|
) }, member.userId)) }),
|
|
1614
|
-
members !== null && visible.length === 0 ? /* @__PURE__ */ (0,
|
|
1779
|
+
members !== null && visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: members.length === 0 ? "This organization has no other members yet, so there is nobody to assign." : "Nobody in this organization matches." }) : null
|
|
1615
1780
|
] })
|
|
1616
1781
|
] })
|
|
1617
1782
|
] })
|
|
@@ -1624,55 +1789,6 @@ var import_core = require("@ai-matrx/records/core");
|
|
|
1624
1789
|
var import_react16 = require("@ai-matrx/records/react");
|
|
1625
1790
|
var import_design_system7 = require("@ai-matrx/design-system");
|
|
1626
1791
|
|
|
1627
|
-
// src/Refusal.tsx
|
|
1628
|
-
var import_design_system5 = require("@ai-matrx/design-system");
|
|
1629
|
-
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1630
|
-
function hintIsMachineIdentity(hint) {
|
|
1631
|
-
return isMachineIdentity(hint);
|
|
1632
|
-
}
|
|
1633
|
-
function hintForAPerson(hint) {
|
|
1634
|
-
const said = (hint ?? "").trim();
|
|
1635
|
-
if (said === "") return null;
|
|
1636
|
-
return isMachineIdentity(said) ? null : said;
|
|
1637
|
-
}
|
|
1638
|
-
function RefusalNotice({
|
|
1639
|
-
error,
|
|
1640
|
-
className,
|
|
1641
|
-
actions
|
|
1642
|
-
}) {
|
|
1643
|
-
const plain = refusalForAPerson(error);
|
|
1644
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1645
|
-
import_design_system5.Alert,
|
|
1646
|
-
{
|
|
1647
|
-
variant: "destructive",
|
|
1648
|
-
className: (0, import_design_system5.cn)("text-xs", className),
|
|
1649
|
-
...plain.forEngineers ? { title: plain.forEngineers } : {},
|
|
1650
|
-
children: [
|
|
1651
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system5.AlertTitle, { className: "text-xs font-medium", children: plain.title }),
|
|
1652
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_design_system5.AlertDescription, { className: "space-y-1 text-xs", children: [
|
|
1653
|
-
plain.sentence.replace(/\.$/, "") === plain.title ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: plain.sentence }),
|
|
1654
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "opacity-80", children: plain.remedy }),
|
|
1655
|
-
plain.forEngineers ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1656
|
-
"p",
|
|
1657
|
-
{
|
|
1658
|
-
className: "sr-only",
|
|
1659
|
-
"aria-hidden": "true",
|
|
1660
|
-
"data-for-engineers": "",
|
|
1661
|
-
...error.sqlstate ? { "data-sqlstate": error.sqlstate } : {},
|
|
1662
|
-
children: plain.forEngineers
|
|
1663
|
-
}
|
|
1664
|
-
) : null,
|
|
1665
|
-
actions
|
|
1666
|
-
] })
|
|
1667
|
-
]
|
|
1668
|
-
}
|
|
1669
|
-
);
|
|
1670
|
-
}
|
|
1671
|
-
function RefusalLine({ error, className }) {
|
|
1672
|
-
const plain = refusalForAPerson(error);
|
|
1673
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: (0, import_design_system5.cn)("text-xs text-destructive", className), title: plain.forEngineers || void 0, children: refusalLineForAPerson(error) });
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
1792
|
// src/whoChangedSource.tsx
|
|
1677
1793
|
var import_react11 = require("react");
|
|
1678
1794
|
var import_react12 = require("@ai-matrx/records/react");
|
|
@@ -2272,7 +2388,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2272
2388
|
startedFrom.current = null;
|
|
2273
2389
|
}, []);
|
|
2274
2390
|
const writeCellNow = (0, import_react15.useCallback)(
|
|
2275
|
-
async (rowId, key, value) => {
|
|
2391
|
+
async (rowId, key, value, resent = false) => {
|
|
2276
2392
|
const id = cellKey(rowId, key);
|
|
2277
2393
|
setOptimistic((held) => ({ ...held, [id]: value }));
|
|
2278
2394
|
setSaving((held) => ({ ...held, [id]: true }));
|
|
@@ -2315,16 +2431,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2315
2431
|
delete next[id];
|
|
2316
2432
|
return next;
|
|
2317
2433
|
});
|
|
2318
|
-
|
|
2434
|
+
const conflict = (0, import_core.asWriteConflict)(written.error, rowId);
|
|
2435
|
+
if (written.error.code === "stale_write") {
|
|
2436
|
+
if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
|
|
2437
|
+
else versions.current.delete(rowId);
|
|
2438
|
+
if (conflict && !resent && !Object.prototype.hasOwnProperty.call(conflict.contested_fields, key)) {
|
|
2439
|
+
await writeCellNow(rowId, key, value, true);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2319
2443
|
setRefusals((held) => ({
|
|
2320
2444
|
...held,
|
|
2321
2445
|
[id]: {
|
|
2322
2446
|
error: written.error,
|
|
2323
|
-
conflict
|
|
2447
|
+
conflict,
|
|
2324
2448
|
attempted: value
|
|
2325
2449
|
}
|
|
2326
2450
|
}));
|
|
2327
2451
|
},
|
|
2452
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2328
2453
|
[client, reload]
|
|
2329
2454
|
);
|
|
2330
2455
|
const writeCell = (0, import_react15.useCallback)(
|
|
@@ -2399,6 +2524,33 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2399
2524
|
},
|
|
2400
2525
|
[reload]
|
|
2401
2526
|
);
|
|
2527
|
+
const keepEditing = (0, import_react15.useCallback)(
|
|
2528
|
+
(rowId, key) => {
|
|
2529
|
+
const held = refusals[cellKey(rowId, key)];
|
|
2530
|
+
if (!held || !writable(rowId)) return;
|
|
2531
|
+
startedFrom.current = shownValue(rowId, key);
|
|
2532
|
+
typed.current = held.attempted;
|
|
2533
|
+
setDraft(held.attempted);
|
|
2534
|
+
setEditing({ rowId, key });
|
|
2535
|
+
},
|
|
2536
|
+
[refusals, writable, shownValue]
|
|
2537
|
+
);
|
|
2538
|
+
const discard = (0, import_react15.useCallback)((rowId, key) => {
|
|
2539
|
+
const id = cellKey(rowId, key);
|
|
2540
|
+
setRefusals((held) => {
|
|
2541
|
+
if (!held[id]) return held;
|
|
2542
|
+
const next = { ...held };
|
|
2543
|
+
delete next[id];
|
|
2544
|
+
return next;
|
|
2545
|
+
});
|
|
2546
|
+
setOptimistic((held) => {
|
|
2547
|
+
if (!(id in held)) return held;
|
|
2548
|
+
const next = { ...held };
|
|
2549
|
+
delete next[id];
|
|
2550
|
+
return next;
|
|
2551
|
+
});
|
|
2552
|
+
setEditing((open) => open && open.rowId === rowId && open.key === key ? null : open);
|
|
2553
|
+
}, []);
|
|
2402
2554
|
const remove = (0, import_react15.useCallback)(
|
|
2403
2555
|
async (rowId) => {
|
|
2404
2556
|
setBusy(true);
|
|
@@ -2450,6 +2602,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2450
2602
|
},
|
|
2451
2603
|
retry,
|
|
2452
2604
|
keepTheirs,
|
|
2605
|
+
keepEditing,
|
|
2606
|
+
discard,
|
|
2453
2607
|
remove,
|
|
2454
2608
|
addRecord,
|
|
2455
2609
|
rowError,
|
|
@@ -2508,7 +2662,7 @@ function GridCell({
|
|
|
2508
2662
|
);
|
|
2509
2663
|
const value = editing ? editing.valueFor(row, field) : row.document?.[field.key];
|
|
2510
2664
|
const state = editing ? editing.stateOf(row.id, field.key) : null;
|
|
2511
|
-
const
|
|
2665
|
+
const refusal2 = editing ? editing.refusalOf(row.id, field.key) : null;
|
|
2512
2666
|
const editable = Boolean(editing) && canWrite && fieldIsEditable(field);
|
|
2513
2667
|
if (open && editing) {
|
|
2514
2668
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -2518,13 +2672,28 @@ function GridCell({
|
|
|
2518
2672
|
draft: editing.draft,
|
|
2519
2673
|
onType: editing.type,
|
|
2520
2674
|
onCommit: (move) => editing.commit(move),
|
|
2521
|
-
onCancel: editing.cancel
|
|
2675
|
+
onCancel: editing.cancel,
|
|
2676
|
+
below: (
|
|
2677
|
+
// KEPT EDITING: the refusal stays beside the value it is about, so the
|
|
2678
|
+
// person reads what to change while changing it. Keep editing is the
|
|
2679
|
+
// state they are in, so only Discard is offered. It is INSIDE the open
|
|
2680
|
+
// cell, so pressing Discard is not "focus left the cell" — which would
|
|
2681
|
+
// commit the refused value one more time on its way out.
|
|
2682
|
+
refusal2 && !refusal2.conflict ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2683
|
+
RefusalNotice,
|
|
2684
|
+
{
|
|
2685
|
+
error: refusal2.error,
|
|
2686
|
+
className: "py-1.5",
|
|
2687
|
+
onDiscard: () => editing.discard(row.id, field.key)
|
|
2688
|
+
}
|
|
2689
|
+
) : null
|
|
2690
|
+
)
|
|
2522
2691
|
}
|
|
2523
2692
|
);
|
|
2524
2693
|
}
|
|
2525
2694
|
if (editorKindFor(field) === "checkbox") {
|
|
2526
2695
|
const answered = value === true || value === false;
|
|
2527
|
-
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { "data-matrx-cell-control": "", className: "flex w-full items-center gap-1.5", children: [
|
|
2696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { "data-matrx-cell-control": "", className: "flex w-full flex-wrap items-center gap-1.5", children: [
|
|
2528
2697
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2529
2698
|
import_design_system7.Checkbox,
|
|
2530
2699
|
{
|
|
@@ -2537,10 +2706,7 @@ function GridCell({
|
|
|
2537
2706
|
}
|
|
2538
2707
|
),
|
|
2539
2708
|
answered ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
|
|
2540
|
-
|
|
2541
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal.error }),
|
|
2542
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
|
|
2543
|
-
] }) : null
|
|
2709
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: false }) : null
|
|
2544
2710
|
] });
|
|
2545
2711
|
}
|
|
2546
2712
|
const openThisCell = (event) => {
|
|
@@ -2613,22 +2779,48 @@ function GridCell({
|
|
|
2613
2779
|
),
|
|
2614
2780
|
onAskWhoChanged ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WhoBadge, { field, row, onAskWhoChanged }) : null,
|
|
2615
2781
|
state === "saving" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
|
|
2616
|
-
|
|
2617
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(RefusalLine, { error: refusal.error }),
|
|
2618
|
-
refusal.conflict ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
|
|
2619
|
-
"Theirs: ",
|
|
2620
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal.conflict, field.key) })
|
|
2621
|
-
] }) : null,
|
|
2622
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex gap-1", children: [
|
|
2623
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
|
|
2624
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
|
|
2625
|
-
] })
|
|
2626
|
-
] }) : null
|
|
2782
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: true }) : null
|
|
2627
2783
|
]
|
|
2628
2784
|
}
|
|
2629
2785
|
)
|
|
2630
2786
|
);
|
|
2631
2787
|
}
|
|
2788
|
+
function CellRefusalNotice({
|
|
2789
|
+
refusal: refusal2,
|
|
2790
|
+
field,
|
|
2791
|
+
row,
|
|
2792
|
+
editing,
|
|
2793
|
+
typed
|
|
2794
|
+
}) {
|
|
2795
|
+
if (refusal2.conflict) {
|
|
2796
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2797
|
+
RefusalNotice,
|
|
2798
|
+
{
|
|
2799
|
+
error: refusal2.error,
|
|
2800
|
+
className: "py-1.5",
|
|
2801
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
|
|
2802
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "text-[11px]", children: [
|
|
2803
|
+
"Theirs: ",
|
|
2804
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: formatTheirs(refusal2.conflict, field.key) })
|
|
2805
|
+
] }),
|
|
2806
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex flex-wrap gap-1", children: [
|
|
2807
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
|
|
2808
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
|
|
2809
|
+
] })
|
|
2810
|
+
] })
|
|
2811
|
+
}
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2815
|
+
RefusalNotice,
|
|
2816
|
+
{
|
|
2817
|
+
error: refusal2.error,
|
|
2818
|
+
className: "py-1.5",
|
|
2819
|
+
...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
|
|
2820
|
+
onDiscard: () => editing?.discard(row.id, field.key)
|
|
2821
|
+
}
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2632
2824
|
function WhoBadge({
|
|
2633
2825
|
field,
|
|
2634
2826
|
row,
|
|
@@ -2681,7 +2873,8 @@ function OpenCell({
|
|
|
2681
2873
|
draft,
|
|
2682
2874
|
onType,
|
|
2683
2875
|
onCommit,
|
|
2684
|
-
onCancel
|
|
2876
|
+
onCancel,
|
|
2877
|
+
below
|
|
2685
2878
|
}) {
|
|
2686
2879
|
const multiline = takesANewline(field);
|
|
2687
2880
|
const blurCommits = closesOnBlur(field);
|
|
@@ -2710,6 +2903,7 @@ function OpenCell({
|
|
|
2710
2903
|
className: "flex w-full min-w-[10rem] flex-col gap-1",
|
|
2711
2904
|
onDoubleClick: (event) => event.stopPropagation(),
|
|
2712
2905
|
onKeyDown: (event) => {
|
|
2906
|
+
if (event.target?.closest?.("[data-refusal-notice]")) return;
|
|
2713
2907
|
if (event.key === "Escape") {
|
|
2714
2908
|
event.preventDefault();
|
|
2715
2909
|
event.stopPropagation();
|
|
@@ -2735,7 +2929,8 @@ function OpenCell({
|
|
|
2735
2929
|
blurCommits ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-muted-foreground", children: multiline ? "\u23CE saves \xB7 \u21E7\u23CE new line \xB7 Esc cancels \xB7 Tab next" : "\u23CE saves \xB7 Esc cancels \xB7 Tab next" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-1", children: [
|
|
2736
2930
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
|
|
2737
2931
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_design_system7.Button, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
|
|
2738
|
-
] })
|
|
2932
|
+
] }),
|
|
2933
|
+
below
|
|
2739
2934
|
]
|
|
2740
2935
|
}
|
|
2741
2936
|
);
|
|
@@ -3316,7 +3511,20 @@ function EnrichPanel({ tableId, fieldId, className }) {
|
|
|
3316
3511
|
" in one go."
|
|
3317
3512
|
] }) : null
|
|
3318
3513
|
] }) : null,
|
|
3319
|
-
outcome ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className:
|
|
3514
|
+
outcome ? outcome.ok ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-muted-foreground", children: outcome.message }) : (
|
|
3515
|
+
// A run that did not happen is a refusal, with a heading and a remedy,
|
|
3516
|
+
// never the host's words in red (lane REFUSAL-SWEEP).
|
|
3517
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3518
|
+
RefusalLine,
|
|
3519
|
+
{
|
|
3520
|
+
error: refusal(
|
|
3521
|
+
"internal",
|
|
3522
|
+
outcome.message,
|
|
3523
|
+
"Nothing was filled in. Try the run again in a moment."
|
|
3524
|
+
)
|
|
3525
|
+
}
|
|
3526
|
+
)
|
|
3527
|
+
) : null,
|
|
3320
3528
|
field ? null : null
|
|
3321
3529
|
] }, row.field_id);
|
|
3322
3530
|
}) });
|
|
@@ -3361,15 +3569,15 @@ function PastePreview({
|
|
|
3361
3569
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PlanTable, { plan }),
|
|
3362
3570
|
plan.refusals.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-matrx-paste-refusals": true, className: "flex flex-col gap-1", children: [
|
|
3363
3571
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs font-medium", children: plan.refusals.length === 1 ? "One cell will be left alone:" : `${plan.refusals.length} cells will be left alone:` }),
|
|
3364
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((
|
|
3572
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { className: "flex flex-col gap-1", children: plan.refusals.slice(0, 12).map((refusal2, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { className: "text-xs text-muted-foreground", children: [
|
|
3365
3573
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "font-medium", children: [
|
|
3366
3574
|
"Row ",
|
|
3367
|
-
|
|
3575
|
+
refusal2.fromLine,
|
|
3368
3576
|
", ",
|
|
3369
|
-
|
|
3577
|
+
refusal2.column
|
|
3370
3578
|
] }),
|
|
3371
3579
|
" \u2014 ",
|
|
3372
|
-
|
|
3580
|
+
refusal2.why
|
|
3373
3581
|
] }, index)) }),
|
|
3374
3582
|
plan.refusals.length > 12 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
3375
3583
|
plan.refusals.length - 12,
|
|
@@ -3397,7 +3605,13 @@ function PastePreview({
|
|
|
3397
3605
|
setAdding(group.fieldKey);
|
|
3398
3606
|
void onAddOptions(group.fieldKey, group.words).catch(
|
|
3399
3607
|
(err) => setAddFailed(
|
|
3400
|
-
|
|
3608
|
+
// The error's own text is an engineer's; the person
|
|
3609
|
+
// reads what did not happen and what to do.
|
|
3610
|
+
asRefusal(
|
|
3611
|
+
err,
|
|
3612
|
+
"Those options were not added, so the pasted words still do not match a choice.",
|
|
3613
|
+
"Try adding them again, or add them from the column's settings."
|
|
3614
|
+
)
|
|
3401
3615
|
)
|
|
3402
3616
|
).finally(() => setAdding(null));
|
|
3403
3617
|
},
|
|
@@ -3405,7 +3619,7 @@ function PastePreview({
|
|
|
3405
3619
|
}
|
|
3406
3620
|
)
|
|
3407
3621
|
] }, group.fieldKey)),
|
|
3408
|
-
addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3622
|
+
addFailed ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RefusalLine, { error: addFailed }) : null
|
|
3409
3623
|
] }) : null,
|
|
3410
3624
|
plan.pastRightEdge > 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
3411
3625
|
plan.pastRightEdge,
|
|
@@ -3585,19 +3799,19 @@ function planPastedBlock(args) {
|
|
|
3585
3799
|
accepted[key] = judged.value;
|
|
3586
3800
|
}
|
|
3587
3801
|
const predicted = (0, import_core2.predictValueRefusals)(args.fields, accepted);
|
|
3588
|
-
for (const
|
|
3589
|
-
const key =
|
|
3802
|
+
for (const refusal2 of predicted) {
|
|
3803
|
+
const key = refusal2.field_key;
|
|
3590
3804
|
if (key === void 0) continue;
|
|
3591
3805
|
const cell = built.find((b) => b.key === key && b.refusal === null);
|
|
3592
3806
|
if (!cell) continue;
|
|
3593
|
-
cell.refusal =
|
|
3807
|
+
cell.refusal = refusal2.message;
|
|
3594
3808
|
cell.value = null;
|
|
3595
3809
|
delete accepted[key];
|
|
3596
3810
|
refusals.push({
|
|
3597
3811
|
fromLine,
|
|
3598
3812
|
column: fieldName(byKey.get(key) ?? { key }),
|
|
3599
3813
|
raw: cell.raw,
|
|
3600
|
-
why:
|
|
3814
|
+
why: refusal2.message
|
|
3601
3815
|
});
|
|
3602
3816
|
}
|
|
3603
3817
|
cells += Object.keys(accepted).length;
|
|
@@ -4361,18 +4575,24 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
4361
4575
|
size: "sm",
|
|
4362
4576
|
variant: "outline",
|
|
4363
4577
|
onClick: () => {
|
|
4578
|
+
const failed = (thrown) => setFailure(
|
|
4579
|
+
refusalFromThrown(
|
|
4580
|
+
thrown,
|
|
4581
|
+
"The spreadsheet could not be written.",
|
|
4582
|
+
"The CSV beside this button carries the same rows."
|
|
4583
|
+
)
|
|
4584
|
+
);
|
|
4585
|
+
setFailure(null);
|
|
4364
4586
|
try {
|
|
4365
|
-
|
|
4587
|
+
(0, import_core3.exportXlsx)(fields.data ?? [], exported(), name).then(
|
|
4366
4588
|
(bytes) => download(
|
|
4367
4589
|
`${name}.xlsx`,
|
|
4368
4590
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
4369
4591
|
bytes
|
|
4370
4592
|
)
|
|
4371
|
-
);
|
|
4593
|
+
).catch(failed);
|
|
4372
4594
|
} catch (thrown) {
|
|
4373
|
-
|
|
4374
|
-
`The spreadsheet could not be written (${thrown instanceof Error ? thrown.message : String(thrown)}). The CSV beside this button carries the same rows.`
|
|
4375
|
-
);
|
|
4595
|
+
failed(thrown);
|
|
4376
4596
|
}
|
|
4377
4597
|
},
|
|
4378
4598
|
children: "XLSX"
|
|
@@ -4388,7 +4608,7 @@ function ExportMenu({ tableId, rows, label, className }) {
|
|
|
4388
4608
|
human: () => (0, import_core3.exportCsv)(fields.data ?? [], exported())
|
|
4389
4609
|
}
|
|
4390
4610
|
),
|
|
4391
|
-
failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
4611
|
+
failure ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefusalLine, { error: failure }) : null
|
|
4392
4612
|
] });
|
|
4393
4613
|
}
|
|
4394
4614
|
|
|
@@ -4489,8 +4709,47 @@ function FilePicker({
|
|
|
4489
4709
|
// src/ImportWizard.tsx
|
|
4490
4710
|
var import_react26 = require("react");
|
|
4491
4711
|
var import_react27 = require("@ai-matrx/records/react");
|
|
4492
|
-
var
|
|
4712
|
+
var import_core5 = require("@ai-matrx/records/core");
|
|
4493
4713
|
var import_design_system13 = require("@ai-matrx/design-system");
|
|
4714
|
+
|
|
4715
|
+
// src/importRuleCheck.ts
|
|
4716
|
+
var import_core4 = require("@ai-matrx/records/core");
|
|
4717
|
+
function checkImportRules(fields, rows, mapping) {
|
|
4718
|
+
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
4719
|
+
const verdicts = [];
|
|
4720
|
+
for (const [header, key] of Object.entries(mapping)) {
|
|
4721
|
+
const field = byKey.get(key);
|
|
4722
|
+
if (!field) continue;
|
|
4723
|
+
let refused = 0;
|
|
4724
|
+
let judged = 0;
|
|
4725
|
+
let first = null;
|
|
4726
|
+
rows.forEach((row, i) => {
|
|
4727
|
+
const raw = String(row[header] ?? "").trim();
|
|
4728
|
+
if (raw === "") return;
|
|
4729
|
+
judged += 1;
|
|
4730
|
+
const meant = valueFromPastedText(field, raw);
|
|
4731
|
+
let why = null;
|
|
4732
|
+
if ("refusal" in meant) why = meant.refusal;
|
|
4733
|
+
else {
|
|
4734
|
+
const predicted = (0, import_core4.predictValueRefusals)([field], { [key]: meant.value }).filter(
|
|
4735
|
+
(p) => p.field_key === void 0 || p.field_key === key
|
|
4736
|
+
);
|
|
4737
|
+
if (predicted.length > 0) why = predicted[0].message;
|
|
4738
|
+
}
|
|
4739
|
+
if (why !== null) {
|
|
4740
|
+
refused += 1;
|
|
4741
|
+
if (!first) first = { why, line: i + 1, raw };
|
|
4742
|
+
}
|
|
4743
|
+
});
|
|
4744
|
+
if (refused > 0 && first) {
|
|
4745
|
+
const f = first;
|
|
4746
|
+
verdicts.push({ header, field, refused, judged, firstWhy: f.why, firstLine: f.line, firstRaw: f.raw });
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
return verdicts;
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
// src/ImportWizard.tsx
|
|
4494
4753
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4495
4754
|
var BATCH = 250;
|
|
4496
4755
|
var SAMPLES = 40;
|
|
@@ -4543,9 +4802,13 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4543
4802
|
const [openRun, setOpenRun] = (0, import_react26.useState)(null);
|
|
4544
4803
|
const [runRows, setRunRows] = (0, import_react26.useState)(null);
|
|
4545
4804
|
const [runProblem, setRunProblem] = (0, import_react26.useState)(null);
|
|
4805
|
+
const [runsProblem, setRunsProblem] = (0, import_react26.useState)(null);
|
|
4546
4806
|
const loadRuns = (0, import_react26.useCallback)(async () => {
|
|
4547
4807
|
const answered = await client.imports({ table_id: tableId, limit: 10 });
|
|
4548
|
-
if (answered.ok)
|
|
4808
|
+
if (answered.ok) {
|
|
4809
|
+
setRuns(answered.data);
|
|
4810
|
+
setRunsProblem(null);
|
|
4811
|
+
} else setRunsProblem(answered.error);
|
|
4549
4812
|
}, [client, tableId]);
|
|
4550
4813
|
(0, import_react26.useEffect)(() => {
|
|
4551
4814
|
void loadRuns();
|
|
@@ -4566,7 +4829,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4566
4829
|
limit: 200
|
|
4567
4830
|
});
|
|
4568
4831
|
if (!answered.ok) {
|
|
4569
|
-
setRunProblem(answered.error
|
|
4832
|
+
setRunProblem(answered.error);
|
|
4570
4833
|
return;
|
|
4571
4834
|
}
|
|
4572
4835
|
setRunRows({
|
|
@@ -4593,7 +4856,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4593
4856
|
try {
|
|
4594
4857
|
const xlsx = file.name.toLowerCase().endsWith(".xlsx");
|
|
4595
4858
|
const bytes = await file.arrayBuffer();
|
|
4596
|
-
const result = xlsx ? await (0,
|
|
4859
|
+
const result = xlsx ? await (0, import_core5.importXlsx)(bytes) : (0, import_core5.importCsv)(new TextDecoder().decode(bytes));
|
|
4597
4860
|
setFormat(xlsx ? "xlsx" : "csv");
|
|
4598
4861
|
setFileName(file.name);
|
|
4599
4862
|
setFileBytes(file.size);
|
|
@@ -4607,7 +4870,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4607
4870
|
}));
|
|
4608
4871
|
const answered = await client.importPlan({ table_id: tableId, columns });
|
|
4609
4872
|
if (!answered.ok) {
|
|
4610
|
-
setProblem(answered.error
|
|
4873
|
+
setProblem(answered.error);
|
|
4611
4874
|
setPhase("waiting");
|
|
4612
4875
|
return;
|
|
4613
4876
|
}
|
|
@@ -4624,7 +4887,14 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4624
4887
|
setMapping(next);
|
|
4625
4888
|
setPhase("ready");
|
|
4626
4889
|
} catch (thrown) {
|
|
4627
|
-
setProblem(
|
|
4890
|
+
setProblem(
|
|
4891
|
+
refusalFromThrown(
|
|
4892
|
+
thrown,
|
|
4893
|
+
"That file could not be read, so nothing was imported.",
|
|
4894
|
+
"Save it again as CSV or Excel and pick it again.",
|
|
4895
|
+
"invalid_argument"
|
|
4896
|
+
)
|
|
4897
|
+
);
|
|
4628
4898
|
setPhase("waiting");
|
|
4629
4899
|
}
|
|
4630
4900
|
},
|
|
@@ -4641,7 +4911,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4641
4911
|
setPhase("declaring");
|
|
4642
4912
|
const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
|
|
4643
4913
|
if (!made.ok) {
|
|
4644
|
-
setProblem(made.error
|
|
4914
|
+
setProblem(made.error);
|
|
4645
4915
|
setPhase("ready");
|
|
4646
4916
|
return;
|
|
4647
4917
|
}
|
|
@@ -4660,7 +4930,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4660
4930
|
policy: { on_duplicate: onDuplicate, unmapped }
|
|
4661
4931
|
});
|
|
4662
4932
|
if (!opened.ok) {
|
|
4663
|
-
setProblem(opened.error
|
|
4933
|
+
setProblem(opened.error);
|
|
4664
4934
|
setPhase("ready");
|
|
4665
4935
|
return;
|
|
4666
4936
|
}
|
|
@@ -4679,7 +4949,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4679
4949
|
const batch = rows.slice(at, at + take2);
|
|
4680
4950
|
const answered = await client.importRows({ import_id: importId, rows: batch, mapping });
|
|
4681
4951
|
if (!answered.ok) {
|
|
4682
|
-
setProblem(answered.error
|
|
4952
|
+
setProblem(answered.error);
|
|
4683
4953
|
setOutcomes([...collected]);
|
|
4684
4954
|
setLedger({ ...told, final: false });
|
|
4685
4955
|
setPhase("ready");
|
|
@@ -4698,7 +4968,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4698
4968
|
}
|
|
4699
4969
|
const finished = await client.importFinish({ import_id: importId, unmapped });
|
|
4700
4970
|
if (!finished.ok) {
|
|
4701
|
-
setProblem(finished.error
|
|
4971
|
+
setProblem(finished.error);
|
|
4702
4972
|
setPhase("done");
|
|
4703
4973
|
return;
|
|
4704
4974
|
}
|
|
@@ -4736,6 +5006,11 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4736
5006
|
const titleMissing = Boolean(titleKey) && titleMappedTo === null;
|
|
4737
5007
|
const titleFieldLabel = (titleKey ? plan?.fields.find((f) => f.key === titleKey)?.label : null) || (titleKey ? declared.find((f) => f.key === titleKey)?.label : null) || "the name column";
|
|
4738
5008
|
const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
|
|
5009
|
+
const ruleVerdicts = (0, import_react26.useMemo)(() => checkImportRules(declared, rows, mapping), [declared, rows, mapping]);
|
|
5010
|
+
const ruleVerdictOf = (0, import_react26.useMemo)(() => new Map(ruleVerdicts.map((v) => [v.header, v])), [ruleVerdicts]);
|
|
5011
|
+
const refusedByRules = ruleVerdicts.reduce((n, v) => n + v.refused, 0);
|
|
5012
|
+
const [goAheadAnyway, setGoAheadAnyway] = (0, import_react26.useState)(false);
|
|
5013
|
+
(0, import_react26.useEffect)(() => setGoAheadAnyway(false), [ruleVerdicts]);
|
|
4739
5014
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: (0, import_design_system13.cn)("flex min-w-0 flex-col gap-2 text-xs", className), children: [
|
|
4740
5015
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4741
5016
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "Import" }),
|
|
@@ -4763,7 +5038,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4763
5038
|
} : {}
|
|
4764
5039
|
}
|
|
4765
5040
|
) : null,
|
|
4766
|
-
problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5041
|
+
problem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: problem }) : null,
|
|
4767
5042
|
phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
|
|
4768
5043
|
phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
|
|
4769
5044
|
plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
@@ -4860,7 +5135,28 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4860
5135
|
column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
|
|
4861
5136
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
|
|
4862
5137
|
column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
|
|
4863
|
-
column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null
|
|
5138
|
+
column.collides_with ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Two columns in this file would make the same column." }) : null,
|
|
5139
|
+
ruleVerdictOf.get(column.header) ? (() => {
|
|
5140
|
+
const v = ruleVerdictOf.get(column.header);
|
|
5141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { "data-import-rule-refusal": column.header, className: "mt-1", children: [
|
|
5142
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5143
|
+
RefusalLine,
|
|
5144
|
+
{
|
|
5145
|
+
error: refusal(
|
|
5146
|
+
"refused_by_rule",
|
|
5147
|
+
`${v.refused} of ${v.judged} row${v.judged === 1 ? "" : "s"} would be refused by ${fieldName(v.field)}'s rules. ${v.firstWhy}`,
|
|
5148
|
+
"Send this column somewhere else, fix those rows in your file, or import the rest and they are listed afterwards."
|
|
5149
|
+
)
|
|
5150
|
+
}
|
|
5151
|
+
),
|
|
5152
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", "data-import-rule-sample": "", children: [
|
|
5153
|
+
"Line ",
|
|
5154
|
+
v.firstLine,
|
|
5155
|
+
" in your file: ",
|
|
5156
|
+
v.firstRaw
|
|
5157
|
+
] })
|
|
5158
|
+
] });
|
|
5159
|
+
})() : null
|
|
4864
5160
|
] }),
|
|
4865
5161
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate pt-1.5 text-muted-foreground", children: (column.samples ?? []).slice(0, 3).map(String).join(" \xB7 ") })
|
|
4866
5162
|
]
|
|
@@ -4924,12 +5220,34 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4924
5220
|
)
|
|
4925
5221
|
] })
|
|
4926
5222
|
] }),
|
|
5223
|
+
ruleVerdicts.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5224
|
+
RefusalNotice,
|
|
5225
|
+
{
|
|
5226
|
+
error: refusal(
|
|
5227
|
+
"refused_by_rule",
|
|
5228
|
+
`${refusedByRules} value${refusedByRules === 1 ? "" : "s"} in this file would be refused by ${ruleVerdicts.length === 1 ? `the rules of ${fieldName(ruleVerdicts[0].field)}` : `the rules of ${ruleVerdicts.length} columns`} before anything is written. Nothing has been written yet.`,
|
|
5229
|
+
"Change where those columns go, or fix the file and pick it again. To go ahead, the rest is written and every refused row is listed afterwards."
|
|
5230
|
+
),
|
|
5231
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "mt-1 flex items-center gap-1.5", "data-import-go-ahead": "", children: [
|
|
5232
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5233
|
+
"input",
|
|
5234
|
+
{
|
|
5235
|
+
type: "checkbox",
|
|
5236
|
+
checked: goAheadAnyway,
|
|
5237
|
+
disabled: busy,
|
|
5238
|
+
onChange: (e) => setGoAheadAnyway(e.target.checked)
|
|
5239
|
+
}
|
|
5240
|
+
),
|
|
5241
|
+
"Import the rest anyway"
|
|
5242
|
+
] })
|
|
5243
|
+
}
|
|
5244
|
+
) : null,
|
|
4927
5245
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4928
5246
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4929
5247
|
import_design_system13.Button,
|
|
4930
5248
|
{
|
|
4931
5249
|
size: "sm",
|
|
4932
|
-
disabled: busy || !rights.write || rows.length === 0 || titleMissing,
|
|
5250
|
+
disabled: busy || !rights.write || rows.length === 0 || titleMissing || ruleVerdicts.length > 0 && !goAheadAnyway,
|
|
4933
5251
|
onClick: () => void run(),
|
|
4934
5252
|
children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
|
|
4935
5253
|
}
|
|
@@ -4984,7 +5302,12 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
4984
5302
|
] }) }),
|
|
4985
5303
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("tbody", { children: interesting.slice(0, 200).map((o) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
|
|
4986
5304
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.row }),
|
|
4987
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className:
|
|
5305
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: o.outcome === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5306
|
+
RefusalLine,
|
|
5307
|
+
{
|
|
5308
|
+
error: refusal("refused_by_rule", o.reason, "Fix this line in your file and import it again.")
|
|
5309
|
+
}
|
|
5310
|
+
) : o.outcome === "duplicate" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: o.reason }) : "written" }),
|
|
4988
5311
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: o.outcome === "refused" ? Object.entries(o.source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") : "" })
|
|
4989
5312
|
] }, `${o.outcome}-${o.row}`)) })
|
|
4990
5313
|
] }) }) : null,
|
|
@@ -5007,6 +5330,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
5007
5330
|
] }) : null
|
|
5008
5331
|
] }) : null
|
|
5009
5332
|
] }) : null,
|
|
5333
|
+
runsProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runsProblem }) : null,
|
|
5010
5334
|
runs && runs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
5011
5335
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
|
|
5012
5336
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium", children: "Imports into this table" }),
|
|
@@ -5039,7 +5363,7 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
5039
5363
|
}
|
|
5040
5364
|
) : null
|
|
5041
5365
|
] }),
|
|
5042
|
-
openRun === r.import_id ? runProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5366
|
+
openRun === r.import_id ? runProblem ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RefusalNotice, { error: runProblem }) : runRows ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
5043
5367
|
runRows.note ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: runRows.note }) : null,
|
|
5044
5368
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "max-h-64 overflow-auto rounded border", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("table", { className: "w-full", children: [
|
|
5045
5369
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
|
|
@@ -5051,7 +5375,16 @@ function ImportWizard({ tableId, onDone, onWrote, className }) {
|
|
|
5051
5375
|
const source = row.source ?? {};
|
|
5052
5376
|
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { className: "border-t align-top", children: [
|
|
5053
5377
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: String(row.row ?? "") }),
|
|
5054
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1
|
|
5378
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "px-2 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5379
|
+
RefusalLine,
|
|
5380
|
+
{
|
|
5381
|
+
error: refusal(
|
|
5382
|
+
"refused_by_rule",
|
|
5383
|
+
String(row.reason ?? "This row was refused."),
|
|
5384
|
+
"Fix this line in your file and import it again."
|
|
5385
|
+
)
|
|
5386
|
+
}
|
|
5387
|
+
) }),
|
|
5055
5388
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("td", { className: "max-w-96 px-2 py-1 text-muted-foreground", children: Object.entries(source).map(([k, v]) => `${k}: ${String(v ?? "")}`).join(" \xB7 ") })
|
|
5056
5389
|
] }, `${r.import_id}-${String(row.row ?? i)}`);
|
|
5057
5390
|
}) })
|
|
@@ -5270,7 +5603,9 @@ function keyFor(label) {
|
|
|
5270
5603
|
if (token === "") return "";
|
|
5271
5604
|
return /^[a-z]/.test(token) ? token : `f_${token}`;
|
|
5272
5605
|
}
|
|
5273
|
-
var
|
|
5606
|
+
var COLUMN_DID_NOT_LAND_SENTENCE = "The store accepted this column and the table does not have it yet. Nothing was lost \u2014 your record values are untouched \u2014 but the column is not there, so this panel is staying open.";
|
|
5607
|
+
var COLUMN_DID_NOT_LAND_REMEDY = "Press Save again; if it keeps happening, tell us, because the store and the table disagree and that is ours to fix.";
|
|
5608
|
+
var COLUMN_DID_NOT_LAND = `${COLUMN_DID_NOT_LAND_SENTENCE} ${COLUMN_DID_NOT_LAND_REMEDY}`;
|
|
5274
5609
|
function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }) {
|
|
5275
5610
|
const table = (0, import_react31.useTable)(tableId);
|
|
5276
5611
|
const fields = (0, import_react31.useFields)(tableId);
|
|
@@ -5570,14 +5905,20 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5570
5905
|
] }, `${rule.kind}-${index}`))
|
|
5571
5906
|
] }),
|
|
5572
5907
|
mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(RefusalNotice, { error: mutation.error }) : null,
|
|
5573
|
-
missing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-
|
|
5574
|
-
notLanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5908
|
+
missing ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { id: "field-editor-missing", className: "text-[11px] text-muted-foreground", "data-field-editor-missing": "", children: missing }) : null,
|
|
5909
|
+
notLanded ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5910
|
+
RefusalNotice,
|
|
5911
|
+
{
|
|
5912
|
+
error: refusal("internal", COLUMN_DID_NOT_LAND_SENTENCE, COLUMN_DID_NOT_LAND_REMEDY)
|
|
5913
|
+
}
|
|
5914
|
+
) : null,
|
|
5575
5915
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
5576
5916
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5577
5917
|
import_design_system15.Button,
|
|
5578
5918
|
{
|
|
5579
5919
|
size: "sm",
|
|
5580
5920
|
disabled: mutation.saving || missing !== null,
|
|
5921
|
+
...missing ? { "aria-describedby": "field-editor-missing" } : {},
|
|
5581
5922
|
onClick: () => void save(),
|
|
5582
5923
|
children: mutation.saving ? "Saving\u2026" : field ? EDIT_FIELD_SAVE_LABEL : ADD_FIELD_SAVE_LABEL
|
|
5583
5924
|
}
|
|
@@ -6555,8 +6896,8 @@ function TableSettings({
|
|
|
6555
6896
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
|
|
6556
6897
|
] }) : null,
|
|
6557
6898
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { className: "divide-y rounded border", children: [
|
|
6558
|
-
rows.map((field, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
6559
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-
|
|
6899
|
+
rows.map((field, index) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { "data-settings-field": "", className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
|
|
6900
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "data-settings-field-name": "", className: "min-w-[8rem] flex-1 break-words", children: fieldName(field) }),
|
|
6560
6901
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "outline", className: "text-[10px] font-normal", children: fieldTypeLabel(field) }),
|
|
6561
6902
|
field.required ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-destructive", children: "required" }) : null,
|
|
6562
6903
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -6613,9 +6954,9 @@ function TableSettings({
|
|
|
6613
6954
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "Proposed" }),
|
|
6614
6955
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
|
|
6615
6956
|
] }),
|
|
6616
|
-
proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
6617
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-
|
|
6618
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-
|
|
6957
|
+
proposals === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "Nothing is listing proposals here yet: the store has no `custom.field_propose` door, so this panel would be inventing an empty queue. When the door lands, its rows appear in this list with the same accept and reject." }) : proposals.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
|
|
6958
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
|
|
6959
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
|
|
6619
6960
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Badge, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
|
|
6620
6961
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "outline", onClick: () => onAcceptProposal?.(proposal), children: "Accept" }),
|
|
6621
6962
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => onRejectProposal?.(proposal), children: "Reject" })
|
|
@@ -6790,7 +7131,7 @@ var import_design_system22 = require("@ai-matrx/design-system");
|
|
|
6790
7131
|
// src/RecordForm.tsx
|
|
6791
7132
|
var import_react41 = require("react");
|
|
6792
7133
|
var import_react42 = require("@ai-matrx/records/react");
|
|
6793
|
-
var
|
|
7134
|
+
var import_core6 = require("@ai-matrx/records/core");
|
|
6794
7135
|
var import_design_system20 = require("@ai-matrx/design-system");
|
|
6795
7136
|
|
|
6796
7137
|
// src/systemTable.ts
|
|
@@ -6980,6 +7321,8 @@ function RecordForm({
|
|
|
6980
7321
|
const mutation = (0, import_react42.useRecordMutation)();
|
|
6981
7322
|
const [draft, setDraft] = (0, import_react41.useState)({});
|
|
6982
7323
|
const [touched, setTouched] = (0, import_react41.useState)(false);
|
|
7324
|
+
const [changed, setChanged] = (0, import_react41.useState)({});
|
|
7325
|
+
const [answered, setAnswered] = (0, import_react41.useState)(null);
|
|
6983
7326
|
const loaded = useRecordVersion(recordId ?? null);
|
|
6984
7327
|
const loadedVersion = loaded.version;
|
|
6985
7328
|
(0, import_react41.useEffect)(() => {
|
|
@@ -6995,13 +7338,43 @@ function RecordForm({
|
|
|
6995
7338
|
return out;
|
|
6996
7339
|
}, [draft]);
|
|
6997
7340
|
const predicted = (0, import_react41.useMemo)(
|
|
6998
|
-
() => fields.data ? (0,
|
|
7341
|
+
() => fields.data ? (0, import_core6.predictWriteRefusals)({ fields: fields.data, document: document2, ...recordType ? { recordType } : {} }) : [],
|
|
6999
7342
|
[fields.data, document2, recordType]
|
|
7000
7343
|
);
|
|
7001
7344
|
if (fields.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: fields.error, className });
|
|
7002
7345
|
if (existing.error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(RefusalNotice, { error: existing.error, className });
|
|
7346
|
+
const storeSaid = mutation.error && mutation.error !== answered ? mutation.error : null;
|
|
7347
|
+
const fieldProblems = predicted.filter((p) => p.field_key);
|
|
7348
|
+
const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
|
|
7349
|
+
const storeRefusalIsOnAField = storeSaid !== null && !mutation.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
|
|
7350
|
+
const stored = existing.data?.document;
|
|
7351
|
+
const focusField = (field) => {
|
|
7352
|
+
if (!field) return;
|
|
7353
|
+
globalThis.document?.getElementById(`field-${field.id}`)?.focus();
|
|
7354
|
+
};
|
|
7355
|
+
const keepEditing = (field) => {
|
|
7356
|
+
if (storeSaid) setAnswered(storeSaid);
|
|
7357
|
+
const first = field ?? (fields.data ?? []).find((f) => problemsShown(f.key).length > 0) ?? (fields.data ?? [])[0];
|
|
7358
|
+
focusField(first);
|
|
7359
|
+
};
|
|
7360
|
+
const discard = (field) => {
|
|
7361
|
+
if (storeSaid) setAnswered(storeSaid);
|
|
7362
|
+
if (field) {
|
|
7363
|
+
setDraft((d) => ({ ...d, [field.key]: stored ? stored[field.key] : void 0 }));
|
|
7364
|
+
setChanged((c) => {
|
|
7365
|
+
const next = { ...c };
|
|
7366
|
+
delete next[field.key];
|
|
7367
|
+
return next;
|
|
7368
|
+
});
|
|
7369
|
+
return;
|
|
7370
|
+
}
|
|
7371
|
+
setDraft(stored ? { ...stored } : {});
|
|
7372
|
+
setChanged({});
|
|
7373
|
+
setTouched(false);
|
|
7374
|
+
};
|
|
7003
7375
|
const save = async () => {
|
|
7004
7376
|
setTouched(true);
|
|
7377
|
+
setAnswered(null);
|
|
7005
7378
|
if (recordId) {
|
|
7006
7379
|
const version = loadedVersion;
|
|
7007
7380
|
const moved = await mutation.update({
|
|
@@ -7038,16 +7411,27 @@ function RecordForm({
|
|
|
7038
7411
|
field,
|
|
7039
7412
|
value: draft[field.key],
|
|
7040
7413
|
document: existing.data?.document,
|
|
7041
|
-
problems:
|
|
7042
|
-
onChange: (value) =>
|
|
7414
|
+
problems: problemsShown(field.key),
|
|
7415
|
+
onChange: (value) => {
|
|
7416
|
+
setDraft((d) => ({ ...d, [field.key]: value }));
|
|
7417
|
+
setChanged((c) => c[field.key] ? c : { ...c, [field.key]: true });
|
|
7418
|
+
},
|
|
7419
|
+
onKeepEditing: () => keepEditing(field),
|
|
7420
|
+
onDiscard: () => discard(field)
|
|
7043
7421
|
},
|
|
7044
7422
|
field.id
|
|
7045
7423
|
)) }),
|
|
7046
|
-
touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
7047
|
-
|
|
7424
|
+
touched ? predicted.filter((p) => !p.field_key).map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
7425
|
+
RefusalNotice,
|
|
7426
|
+
{
|
|
7427
|
+
error: { code: "refused_by_rule", message: p.message }
|
|
7428
|
+
},
|
|
7429
|
+
p.message
|
|
7430
|
+
)) : null,
|
|
7431
|
+
storeSaid && !storeRefusalIsOnAField ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
7048
7432
|
RefusalNotice,
|
|
7049
7433
|
{
|
|
7050
|
-
error:
|
|
7434
|
+
error: storeSaid,
|
|
7051
7435
|
...mutation.conflict ? {
|
|
7052
7436
|
actions: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "mt-1 space-y-1", children: [
|
|
7053
7437
|
Object.entries(mutation.conflict.contested_fields).map(([key, theirs]) => {
|
|
@@ -7068,14 +7452,16 @@ function RecordForm({
|
|
|
7068
7452
|
size: "sm",
|
|
7069
7453
|
variant: "outline",
|
|
7070
7454
|
onClick: () => {
|
|
7455
|
+
if (mutation.conflict) loaded.note(mutation.conflict.current_version);
|
|
7071
7456
|
existing.reload();
|
|
7072
7457
|
mutation.clearConflict();
|
|
7458
|
+
setAnswered(storeSaid);
|
|
7073
7459
|
},
|
|
7074
7460
|
children: "Take theirs and start again"
|
|
7075
7461
|
}
|
|
7076
7462
|
)
|
|
7077
7463
|
] })
|
|
7078
|
-
} : {}
|
|
7464
|
+
} : { onKeepEditing: () => keepEditing(), onDiscard: () => discard() }
|
|
7079
7465
|
}
|
|
7080
7466
|
) : null,
|
|
7081
7467
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_design_system20.Separator, {}),
|
|
@@ -7092,13 +7478,23 @@ function FieldRow({
|
|
|
7092
7478
|
value,
|
|
7093
7479
|
document: document2,
|
|
7094
7480
|
problems,
|
|
7095
|
-
onChange
|
|
7481
|
+
onChange,
|
|
7482
|
+
onKeepEditing,
|
|
7483
|
+
onDiscard
|
|
7096
7484
|
}) {
|
|
7097
7485
|
const id = `field-${field.id}`;
|
|
7098
7486
|
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
7099
7487
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
|
|
7100
7488
|
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(FieldControl, { field, value, onChange, id }),
|
|
7101
|
-
problems.map((problem) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
7489
|
+
problems.map((problem, index) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
7490
|
+
RefusalNotice,
|
|
7491
|
+
{
|
|
7492
|
+
error: { code: "refused_by_rule", message: problem.message },
|
|
7493
|
+
className: "border-0 p-0",
|
|
7494
|
+
...index === 0 ? { onKeepEditing, onDiscard } : {}
|
|
7495
|
+
},
|
|
7496
|
+
problem.message
|
|
7497
|
+
))
|
|
7102
7498
|
] });
|
|
7103
7499
|
}
|
|
7104
7500
|
function asWords(value) {
|
|
@@ -7124,12 +7520,16 @@ function ShareControl({
|
|
|
7124
7520
|
subjectId,
|
|
7125
7521
|
name,
|
|
7126
7522
|
may,
|
|
7523
|
+
initiallyOpen = false,
|
|
7127
7524
|
size = "sm",
|
|
7128
7525
|
variant = "ghost",
|
|
7129
7526
|
className
|
|
7130
7527
|
}) {
|
|
7131
7528
|
const host = useRecordsUi();
|
|
7132
|
-
const [open, setOpen] = (0, import_react43.useState)(
|
|
7529
|
+
const [open, setOpen] = (0, import_react43.useState)(initiallyOpen);
|
|
7530
|
+
(0, import_react43.useEffect)(() => {
|
|
7531
|
+
if (initiallyOpen) setOpen(true);
|
|
7532
|
+
}, [initiallyOpen]);
|
|
7133
7533
|
const asked = useRecordRights(may === void 0 ? subjectId : null);
|
|
7134
7534
|
const mayShare = may ?? asked.share;
|
|
7135
7535
|
if (!host.share) return null;
|
|
@@ -7261,6 +7661,7 @@ function computedInPlainWords(row, field) {
|
|
|
7261
7661
|
}
|
|
7262
7662
|
|
|
7263
7663
|
// src/views.ts
|
|
7664
|
+
var import_core7 = require("@ai-matrx/records/core");
|
|
7264
7665
|
var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery"];
|
|
7265
7666
|
var LAYOUT_LABEL = {
|
|
7266
7667
|
grid: "Grid",
|
|
@@ -7384,6 +7785,18 @@ function viewFromRecord(row) {
|
|
|
7384
7785
|
presentation: parseGridPresentation(data["presentation"])
|
|
7385
7786
|
};
|
|
7386
7787
|
}
|
|
7788
|
+
async function saveViewPatch(client, viewId, document2, expectedVersion) {
|
|
7789
|
+
const send = (expected) => client.recordUpdate({
|
|
7790
|
+
record_id: viewId,
|
|
7791
|
+
patch: document2,
|
|
7792
|
+
...expected === null ? {} : { expectedVersion: expected }
|
|
7793
|
+
});
|
|
7794
|
+
const first = await send(expectedVersion);
|
|
7795
|
+
if (first.ok) return first;
|
|
7796
|
+
const conflict = (0, import_core7.asWriteConflict)(first.error, viewId);
|
|
7797
|
+
if (!conflict) return first;
|
|
7798
|
+
return send(conflict.current_version);
|
|
7799
|
+
}
|
|
7387
7800
|
function viewPatchDocument(patch) {
|
|
7388
7801
|
const out = {};
|
|
7389
7802
|
if (patch.name !== void 0) out["name"] = patch.name;
|
|
@@ -7707,10 +8120,22 @@ function Held({
|
|
|
7707
8120
|
);
|
|
7708
8121
|
}
|
|
7709
8122
|
if (pending.kind === "refused") {
|
|
7710
|
-
return
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
8123
|
+
return (
|
|
8124
|
+
// THE STAGE GATE SAID NO: the one refusal surface, heading and remedy both,
|
|
8125
|
+
// never its sentence alone in a red box (lane REFUSAL-SWEEP).
|
|
8126
|
+
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
8127
|
+
RefusalNotice,
|
|
8128
|
+
{
|
|
8129
|
+
className: "mt-1",
|
|
8130
|
+
error: refusal(
|
|
8131
|
+
"refused_by_rule",
|
|
8132
|
+
pending.verdict.why,
|
|
8133
|
+
"The card stays where it was. Fill in what this stage asks for, then move it again."
|
|
8134
|
+
),
|
|
8135
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_design_system23.Button, { size: "sm", variant: "ghost", className: "mt-1 h-6", onClick: onCancel, children: "Close" })
|
|
8136
|
+
}
|
|
8137
|
+
)
|
|
8138
|
+
);
|
|
7714
8139
|
}
|
|
7715
8140
|
if (pending.kind === "approval") {
|
|
7716
8141
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "mt-1 flex flex-col gap-1 rounded border p-2 text-xs", children: [
|
|
@@ -8274,7 +8699,7 @@ function ArchivedView({
|
|
|
8274
8699
|
const [loading, setLoading] = (0, import_react51.useState)(true);
|
|
8275
8700
|
const [error, setError] = (0, import_react51.useState)(null);
|
|
8276
8701
|
const [restoring, setRestoring] = (0, import_react51.useState)(null);
|
|
8277
|
-
const [
|
|
8702
|
+
const [refusal2, setRefusal] = (0, import_react51.useState)(null);
|
|
8278
8703
|
(0, import_react51.useEffect)(() => {
|
|
8279
8704
|
if (laneFromHost) setLane(laneFromHost);
|
|
8280
8705
|
}, [laneFromHost]);
|
|
@@ -8351,7 +8776,7 @@ function ArchivedView({
|
|
|
8351
8776
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
8352
8777
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm", children: recordName(row.document, titleKey) }),
|
|
8353
8778
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-xs text-muted-foreground", "data-testid": "archived-who", children: (0, import_records7.archivedByLine)(row) }),
|
|
8354
|
-
|
|
8779
|
+
refusal2 && refusal2.recordId === row.id ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RefusalNotice, { error: refusal2.error, className: "mt-2" }) : null
|
|
8355
8780
|
] }),
|
|
8356
8781
|
mayRestore ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
8357
8782
|
import_design_system25.Button,
|
|
@@ -8425,7 +8850,7 @@ function ArchivedPortals({
|
|
|
8425
8850
|
const [confirming, setConfirming] = (0, import_react53.useState)(null);
|
|
8426
8851
|
const [typed, setTyped] = (0, import_react53.useState)("");
|
|
8427
8852
|
const [restoring, setRestoring] = (0, import_react53.useState)(null);
|
|
8428
|
-
const [
|
|
8853
|
+
const [refusal2, setRefusal] = (0, import_react53.useState)(null);
|
|
8429
8854
|
const [said, setSaid] = (0, import_react53.useState)(null);
|
|
8430
8855
|
const read = (0, import_react53.useCallback)(async () => {
|
|
8431
8856
|
const answered = await client.listPortals({ archived: "archived" });
|
|
@@ -8541,7 +8966,7 @@ function ArchivedPortals({
|
|
|
8541
8966
|
)
|
|
8542
8967
|
] })
|
|
8543
8968
|
] }) : null,
|
|
8544
|
-
|
|
8969
|
+
refusal2 && refusal2.portalId === portal.portal_id ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RefusalNotice, { error: refusal2.error }) : null
|
|
8545
8970
|
]
|
|
8546
8971
|
},
|
|
8547
8972
|
portal.portal_id
|
|
@@ -9106,7 +9531,7 @@ function MyChecklistSteps({ className }) {
|
|
|
9106
9531
|
const client = (0, import_react62.useRecordsClient)();
|
|
9107
9532
|
const { steps, loading, error, refresh } = useMyChecklistSteps();
|
|
9108
9533
|
const [busy, setBusy] = (0, import_react61.useState)(null);
|
|
9109
|
-
const [
|
|
9534
|
+
const [refusal2, setRefusal] = (0, import_react61.useState)(null);
|
|
9110
9535
|
const complete = (0, import_react61.useCallback)(
|
|
9111
9536
|
async (step2, evidence) => {
|
|
9112
9537
|
setBusy(step2.step_id);
|
|
@@ -9129,7 +9554,7 @@ function MyChecklistSteps({ className }) {
|
|
|
9129
9554
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "tabular-nums", children: steps.length })
|
|
9130
9555
|
] }),
|
|
9131
9556
|
error ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error }) : null,
|
|
9132
|
-
|
|
9557
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(RefusalNotice, { error: refusal2 }) : null,
|
|
9133
9558
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("ol", { className: "flex flex-col gap-1.5", children: steps.map((step2) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
9134
9559
|
StepRow,
|
|
9135
9560
|
{
|
|
@@ -9306,7 +9731,7 @@ function ChecklistTemplateEditor({
|
|
|
9306
9731
|
const client = (0, import_react62.useRecordsClient)();
|
|
9307
9732
|
const [name, setName] = (0, import_react61.useState)("");
|
|
9308
9733
|
const [rows, setRows] = (0, import_react61.useState)([{ ...EMPTY_ROW }]);
|
|
9309
|
-
const [
|
|
9734
|
+
const [refusal2, setRefusal] = (0, import_react61.useState)(null);
|
|
9310
9735
|
const [error, setError] = (0, import_react61.useState)(null);
|
|
9311
9736
|
const [busy, setBusy] = (0, import_react61.useState)(false);
|
|
9312
9737
|
const [loading, setLoading] = (0, import_react61.useState)(Boolean(templateId));
|
|
@@ -9474,9 +9899,9 @@ function ChecklistTemplateEditor({
|
|
|
9474
9899
|
] }, index)) }),
|
|
9475
9900
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
9476
9901
|
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", variant: "outline", onClick: () => setRows((held) => [...held, { ...EMPTY_ROW }]), children: "Add a step" }),
|
|
9477
|
-
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", disabled: busy ||
|
|
9902
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_design_system29.Button, { size: "sm", disabled: busy || refusal2 !== null || spec.steps.length === 0, onClick: () => void save(), children: busy ? "\u2026" : "Save" })
|
|
9478
9903
|
] }),
|
|
9479
|
-
|
|
9904
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-xs text-muted-foreground", "data-testid": "checklist-editor-refusal", children: refusal2 }) : null
|
|
9480
9905
|
] });
|
|
9481
9906
|
}
|
|
9482
9907
|
function toStepSpec(row, index) {
|
|
@@ -10769,6 +11194,7 @@ function BuildOrAsk({
|
|
|
10769
11194
|
|
|
10770
11195
|
// src/PortalsPanel.tsx
|
|
10771
11196
|
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
11197
|
+
var PORTAL_NOT_HERE_LINE = "The link named a portal this table is not part of \u2014 it may have been archived, or it shows other tables. The portals this table is in are listed here.";
|
|
10772
11198
|
function revokeConsequence(person, portalTitle) {
|
|
10773
11199
|
const who = person.client ? `${person.email} (${person.client})` : person.email;
|
|
10774
11200
|
return `${who} will no longer be able to sign in to \u201C${portalTitle}\u201D, and the records that named their client will stop reaching them. Nothing is deleted \u2014 you can invite them again.`;
|
|
@@ -10798,13 +11224,16 @@ function stateWords(person) {
|
|
|
10798
11224
|
};
|
|
10799
11225
|
}
|
|
10800
11226
|
var PORTAL_SUGGESTION = "Let each of my customers sign in and see their own jobs and invoices, and nothing else.";
|
|
10801
|
-
function PortalsPanel({ tableId, className }) {
|
|
11227
|
+
function PortalsPanel({ tableId, activePortalId, className }) {
|
|
10802
11228
|
const client = (0, import_react74.useRecordsClient)();
|
|
10803
11229
|
const host = useRecordsUi();
|
|
10804
11230
|
const [portals, setPortals] = (0, import_react73.useState)(null);
|
|
10805
11231
|
const [exposures, setExposures] = (0, import_react73.useState)([]);
|
|
10806
11232
|
const [listError, setListError] = (0, import_react73.useState)(null);
|
|
10807
|
-
const [openId, setOpenId] = (0, import_react73.useState)(null);
|
|
11233
|
+
const [openId, setOpenId] = (0, import_react73.useState)(activePortalId ?? null);
|
|
11234
|
+
(0, import_react73.useEffect)(() => {
|
|
11235
|
+
if (activePortalId) setOpenId(activePortalId);
|
|
11236
|
+
}, [activePortalId]);
|
|
10808
11237
|
const [building, setBuilding] = (0, import_react73.useState)(false);
|
|
10809
11238
|
const [adding, setAdding] = (0, import_react73.useState)(null);
|
|
10810
11239
|
const [archiveToken, setArchiveToken] = (0, import_react73.useState)(0);
|
|
@@ -10901,59 +11330,73 @@ function PortalsPanel({ tableId, className }) {
|
|
|
10901
11330
|
portal.portal_id
|
|
10902
11331
|
)) })
|
|
10903
11332
|
] }) : null,
|
|
11333
|
+
activePortalId && !shown.some((p) => p.portal_id === activePortalId) ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: PORTAL_NOT_HERE_LINE }) : null,
|
|
10904
11334
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("ul", { className: "flex flex-col gap-2", children: shown.map((portal) => {
|
|
10905
11335
|
const url = `${origin}${(0, import_records9.portalPath)(portal.slug)}`;
|
|
10906
11336
|
const open = openId === portal.portal_id;
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
"."
|
|
10917
|
-
] }),
|
|
10918
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
|
|
10919
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
|
|
10920
|
-
" signed in",
|
|
10921
|
-
" \xB7 ",
|
|
10922
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
|
|
10923
|
-
" invited and waiting"
|
|
10924
|
-
] }),
|
|
10925
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
|
|
10926
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
|
|
10927
|
-
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
10928
|
-
import_design_system36.Button,
|
|
10929
|
-
{
|
|
10930
|
-
size: "sm",
|
|
10931
|
-
variant: "ghost",
|
|
10932
|
-
onClick: () => setOpenId(open ? null : portal.portal_id),
|
|
10933
|
-
children: open ? "Close" : "Open it"
|
|
10934
|
-
}
|
|
11337
|
+
const linked = portal.portal_id === activePortalId;
|
|
11338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)(
|
|
11339
|
+
"li",
|
|
11340
|
+
{
|
|
11341
|
+
"data-linked": linked ? "true" : void 0,
|
|
11342
|
+
ref: linked ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
|
|
11343
|
+
className: (0, import_design_system36.cn)(
|
|
11344
|
+
"rounded-md border border-border bg-card p-2.5",
|
|
11345
|
+
linked && "border-primary ring-1 ring-primary"
|
|
10935
11346
|
),
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
portal,
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
11347
|
+
children: [
|
|
11348
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
11349
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: portal.title }),
|
|
11350
|
+
portal.is_active ? null : /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(import_design_system36.Badge, { variant: "outline", children: "closed" })
|
|
11351
|
+
] }),
|
|
11352
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
|
|
11353
|
+
"Clients come from ",
|
|
11354
|
+
portal.client_table,
|
|
11355
|
+
portal.tables === 1 ? ", and see 1 Table" : `, and see ${portal.tables} Tables`,
|
|
11356
|
+
"."
|
|
11357
|
+
] }),
|
|
11358
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("p", { className: "mt-1.5 text-xs", children: [
|
|
11359
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.signed_in }),
|
|
11360
|
+
" signed in",
|
|
11361
|
+
" \xB7 ",
|
|
11362
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "font-medium", children: portal.invited - portal.signed_in }),
|
|
11363
|
+
" invited and waiting"
|
|
11364
|
+
] }),
|
|
11365
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
|
|
11366
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(CopyLink, { url }),
|
|
11367
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11368
|
+
import_design_system36.Button,
|
|
11369
|
+
{
|
|
11370
|
+
size: "sm",
|
|
11371
|
+
variant: "ghost",
|
|
11372
|
+
onClick: () => setOpenId(open ? null : portal.portal_id),
|
|
11373
|
+
children: open ? "Close" : "Open it"
|
|
11374
|
+
}
|
|
11375
|
+
),
|
|
11376
|
+
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11377
|
+
ArchivePortalControl,
|
|
11378
|
+
{
|
|
11379
|
+
portal,
|
|
11380
|
+
onArchived: () => {
|
|
11381
|
+
setArchiveToken((t) => t + 1);
|
|
11382
|
+
void load();
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11385
|
+
)
|
|
11386
|
+
] }),
|
|
11387
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
11388
|
+
PortalDetail,
|
|
11389
|
+
{
|
|
11390
|
+
portalId: portal.portal_id,
|
|
11391
|
+
...tableId ? { tableId } : {},
|
|
11392
|
+
origin,
|
|
11393
|
+
onChanged: () => void load()
|
|
10943
11394
|
}
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
{
|
|
10950
|
-
portalId: portal.portal_id,
|
|
10951
|
-
...tableId ? { tableId } : {},
|
|
10952
|
-
origin,
|
|
10953
|
-
onChanged: () => void load()
|
|
10954
|
-
}
|
|
10955
|
-
) : null
|
|
10956
|
-
] }, portal.portal_id);
|
|
11395
|
+
) : null
|
|
11396
|
+
]
|
|
11397
|
+
},
|
|
11398
|
+
portal.portal_id
|
|
11399
|
+
);
|
|
10957
11400
|
}) }),
|
|
10958
11401
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ArchivedPortals, { refreshToken: archiveToken, onRestored: () => void load() })
|
|
10959
11402
|
] });
|
|
@@ -11711,7 +12154,17 @@ function DigestScheduler({
|
|
|
11711
12154
|
preview ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
|
|
11712
12155
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "font-medium", children: preview.subject }),
|
|
11713
12156
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
|
|
11714
|
-
preview.incomplete ?
|
|
12157
|
+
preview.incomplete ? (
|
|
12158
|
+
// The summary could not be finished: a refusal line naming the missing
|
|
12159
|
+
// piece and what to do, never the bare sentence in red (lane REFUSAL-SWEEP).
|
|
12160
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
12161
|
+
RefusalLine,
|
|
12162
|
+
{
|
|
12163
|
+
className: "mt-1",
|
|
12164
|
+
error: refusal("not_supported", preview.incomplete, "Fill in the missing piece above, then preview again.")
|
|
12165
|
+
}
|
|
12166
|
+
)
|
|
12167
|
+
) : null,
|
|
11715
12168
|
preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("p", { className: "mt-1", children: [
|
|
11716
12169
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "font-medium", children: "Arrived:" }),
|
|
11717
12170
|
" ",
|
|
@@ -11733,14 +12186,30 @@ var import_react77 = require("react");
|
|
|
11733
12186
|
var import_react78 = require("@ai-matrx/records/react");
|
|
11734
12187
|
var import_design_system38 = require("@ai-matrx/design-system");
|
|
11735
12188
|
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
12189
|
+
var RULE_NOT_HERE_LINE = "The link named a digest that is not one of yours on this table \u2014 it belongs to somebody else, or it was switched off for good. Yours are listed here; its owner can change theirs.";
|
|
11736
12190
|
function whenItFires(subscription) {
|
|
11737
12191
|
if (subscription.cadence === "instant") return "as it happens";
|
|
11738
12192
|
const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
|
|
11739
12193
|
return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
|
|
11740
12194
|
}
|
|
12195
|
+
var PERIOD_MS = {
|
|
12196
|
+
hourly: 60 * 60 * 1e3,
|
|
12197
|
+
daily: 24 * 60 * 60 * 1e3,
|
|
12198
|
+
weekly: 7 * 24 * 60 * 60 * 1e3
|
|
12199
|
+
};
|
|
12200
|
+
function nextSummaryAt(subscription, now) {
|
|
12201
|
+
if (!subscription.next_digest_at) return null;
|
|
12202
|
+
const due = new Date(subscription.next_digest_at);
|
|
12203
|
+
if (Number.isNaN(due.getTime())) return null;
|
|
12204
|
+
if (due.getTime() > now.getTime()) return due;
|
|
12205
|
+
const period = PERIOD_MS[subscription.cadence];
|
|
12206
|
+
if (!period) return due;
|
|
12207
|
+
const behind = now.getTime() - due.getTime();
|
|
12208
|
+
return new Date(due.getTime() + (Math.floor(behind / period) + 1) * period);
|
|
12209
|
+
}
|
|
11741
12210
|
var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
11742
12211
|
var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
|
|
11743
|
-
function SubscriptionsPanel({ tableId, className }) {
|
|
12212
|
+
function SubscriptionsPanel({ tableId, activeRuleId, className }) {
|
|
11744
12213
|
const client = (0, import_react78.useRecordsClient)();
|
|
11745
12214
|
const [rows, setRows] = (0, import_react77.useState)(null);
|
|
11746
12215
|
const [error, setError] = (0, import_react77.useState)(null);
|
|
@@ -11831,81 +12300,104 @@ function SubscriptionsPanel({ tableId, className }) {
|
|
|
11831
12300
|
children: "Nothing is telling you about this table. A summary arrives on a schedule you set and names what arrived, what left and what changed since the last one."
|
|
11832
12301
|
}
|
|
11833
12302
|
) : null,
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
"
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
" ",
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
12303
|
+
activeRuleId && !rows.some((r) => r.rule_id === activeRuleId) ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: RULE_NOT_HERE_LINE }) : null,
|
|
12304
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("ul", { className: "flex flex-col gap-2", children: rows.map((subscription) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
12305
|
+
"li",
|
|
12306
|
+
{
|
|
12307
|
+
"data-linked": subscription.rule_id === activeRuleId ? "true" : void 0,
|
|
12308
|
+
ref: subscription.rule_id === activeRuleId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
|
|
12309
|
+
className: (0, import_design_system38.cn)(
|
|
12310
|
+
"rounded-md border p-2.5",
|
|
12311
|
+
subscription.rule_id === activeRuleId && "border-primary ring-1 ring-primary"
|
|
12312
|
+
),
|
|
12313
|
+
children: [
|
|
12314
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "flex items-start gap-2", children: [
|
|
12315
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
12316
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "truncate text-sm font-medium", children: subscription.name }),
|
|
12317
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: [
|
|
12318
|
+
CHANNEL_WORDS2[subscription.channel] ?? `on the ${subscription.channel} channel`,
|
|
12319
|
+
" \xB7 ",
|
|
12320
|
+
whenItFires(subscription)
|
|
12321
|
+
] })
|
|
12322
|
+
] }),
|
|
12323
|
+
subscription.i_may_mute ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
12324
|
+
import_design_system38.Switch,
|
|
12325
|
+
{
|
|
12326
|
+
checked: !subscription.muted,
|
|
12327
|
+
disabled: busy === subscription.rule_id,
|
|
12328
|
+
"aria-label": `Tell me about ${subscription.name}`,
|
|
12329
|
+
onCheckedChange: (on) => void flip(subscription, on)
|
|
12330
|
+
}
|
|
12331
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Badge, { variant: "secondary", children: "someone else's" })
|
|
12332
|
+
] }),
|
|
12333
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: subscription.muted ? "Off \u2014 it stays here and tells nobody until you switch it back on." : subscription.mine ? "On, and addressed to you." : "On, and addressed to somebody else in this organization." }),
|
|
12334
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
12335
|
+
subscription.next_digest_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
|
|
12336
|
+
"Next summary ",
|
|
12337
|
+
nextSummaryAt(subscription, /* @__PURE__ */ new Date())?.toLocaleString()
|
|
12338
|
+
] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "No next summary while it is off." }) : null,
|
|
12339
|
+
subscription.last_sent_at ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
|
|
12340
|
+
"Last told you ",
|
|
12341
|
+
new Date(subscription.last_sent_at).toLocaleString()
|
|
12342
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { children: "It has not told you anything yet." }),
|
|
12343
|
+
subscription.quiet_hours ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
|
|
12344
|
+
"Not between ",
|
|
12345
|
+
subscription.quiet_hours.start,
|
|
12346
|
+
" and ",
|
|
12347
|
+
subscription.quiet_hours.end,
|
|
12348
|
+
" \u2014 a send inside those hours waits until they end."
|
|
12349
|
+
] }) : null,
|
|
12350
|
+
subscription.cadence === "instant" ? null : /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
12351
|
+
import_design_system38.Button,
|
|
12352
|
+
{
|
|
12353
|
+
size: "sm",
|
|
12354
|
+
variant: "ghost",
|
|
12355
|
+
disabled: busy === subscription.rule_id,
|
|
12356
|
+
onClick: () => void showOne(subscription),
|
|
12357
|
+
children: "Send me a preview now"
|
|
12358
|
+
}
|
|
12359
|
+
)
|
|
12360
|
+
] }),
|
|
12361
|
+
preview && preview.rule_id === subscription.rule_id ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "mt-2 rounded-md border bg-muted/40 p-2.5 text-xs", children: [
|
|
12362
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "font-medium", children: preview.subject }),
|
|
12363
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1 text-muted-foreground", children: preview.body }),
|
|
12364
|
+
preview.incomplete ? (
|
|
12365
|
+
// Absent or honest: a subscription that cannot produce a summary
|
|
12366
|
+
// says which piece is missing instead of showing an empty one.
|
|
12367
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
12368
|
+
RefusalLine,
|
|
12369
|
+
{
|
|
12370
|
+
className: "mt-1",
|
|
12371
|
+
error: refusal(
|
|
12372
|
+
"not_supported",
|
|
12373
|
+
preview.incomplete,
|
|
12374
|
+
"Fill in the missing piece, then preview again."
|
|
12375
|
+
)
|
|
12376
|
+
}
|
|
12377
|
+
)
|
|
12378
|
+
) : null,
|
|
12379
|
+
preview.entered.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
|
|
12380
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Arrived:" }),
|
|
12381
|
+
" ",
|
|
12382
|
+
preview.entered.map((e) => e.name).join(", ")
|
|
12383
|
+
] }) : null,
|
|
12384
|
+
preview.left.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
|
|
12385
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Left:" }),
|
|
12386
|
+
" ",
|
|
12387
|
+
preview.left.map((e) => e.name).join(", ")
|
|
12388
|
+
] }) : null,
|
|
12389
|
+
preview.changed.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("p", { className: "mt-1", children: [
|
|
12390
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "font-medium", children: "Changed:" }),
|
|
12391
|
+
" ",
|
|
12392
|
+
preview.changed.map((e) => e.name).join(", ")
|
|
12393
|
+
] }) : null,
|
|
12394
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { className: "mt-1.5 text-muted-foreground", children: "Nothing was sent and nothing was recorded \u2014 this is what the next one would say." }),
|
|
12395
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(import_design_system38.Button, { size: "sm", variant: "ghost", className: "mt-1", onClick: () => setPreview(null), children: "Close" })
|
|
12396
|
+
] }) : null
|
|
12397
|
+
]
|
|
12398
|
+
},
|
|
12399
|
+
subscription.rule_id
|
|
12400
|
+
)) })
|
|
11909
12401
|
] });
|
|
11910
12402
|
}
|
|
11911
12403
|
|
|
@@ -11960,9 +12452,16 @@ function ConnectedFormRunner(props) {
|
|
|
11960
12452
|
const submit = (0, import_react81.useCallback)(
|
|
11961
12453
|
async (values) => {
|
|
11962
12454
|
if (!client) {
|
|
12455
|
+
const said = "This form cannot send answers from here, so nothing was sent.";
|
|
11963
12456
|
return {
|
|
11964
12457
|
ok: false,
|
|
11965
|
-
message:
|
|
12458
|
+
message: said,
|
|
12459
|
+
error: refusal(
|
|
12460
|
+
"not_supported",
|
|
12461
|
+
said,
|
|
12462
|
+
"Open the form from its own link and answer it there.",
|
|
12463
|
+
"No submit port: a public form is served with its questions and its submit port by the server; a signed-in one needs <RecordsProvider> above it."
|
|
12464
|
+
)
|
|
11966
12465
|
};
|
|
11967
12466
|
}
|
|
11968
12467
|
const document2 = {
|
|
@@ -11975,7 +12474,7 @@ function ConnectedFormRunner(props) {
|
|
|
11975
12474
|
})
|
|
11976
12475
|
};
|
|
11977
12476
|
const written = await client.recordWrite({ table_id: form.subject, data: document2 });
|
|
11978
|
-
if (!written.ok) return { ok: false, message: written.error.message };
|
|
12477
|
+
if (!written.ok) return { ok: false, message: written.error.message, error: written.error };
|
|
11979
12478
|
return { ok: true, recordId: written.data };
|
|
11980
12479
|
},
|
|
11981
12480
|
[client, form]
|
|
@@ -12017,7 +12516,7 @@ function FormStage({
|
|
|
12017
12516
|
const [answers, setAnswers] = (0, import_react81.useState)({});
|
|
12018
12517
|
const [at, setAt] = (0, import_react81.useState)(0);
|
|
12019
12518
|
const [error, setError] = (0, import_react81.useState)(null);
|
|
12020
|
-
const [
|
|
12519
|
+
const [refusal2, setRefusal] = (0, import_react81.useState)(null);
|
|
12021
12520
|
const [writing, setWriting] = (0, import_react81.useState)(false);
|
|
12022
12521
|
const [done, setDone] = (0, import_react81.useState)(null);
|
|
12023
12522
|
const [hidden, setHidden] = (0, import_react81.useState)({});
|
|
@@ -12086,12 +12585,21 @@ function FormStage({
|
|
|
12086
12585
|
const outcome = await onSubmit(values);
|
|
12087
12586
|
setWriting(false);
|
|
12088
12587
|
if (!outcome.ok) {
|
|
12089
|
-
|
|
12588
|
+
const inWords = (text) => questions.reduce(
|
|
12589
|
+
(said, q) => said.replace(new RegExp(`\\b${q.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g"), q.ask),
|
|
12590
|
+
text
|
|
12591
|
+
);
|
|
12592
|
+
const told = outcome.error ?? refusal("invalid_argument", outcome.message, "Check your answers, then send again.");
|
|
12593
|
+
setRefusal({
|
|
12594
|
+
...told,
|
|
12595
|
+
message: inWords(told.message),
|
|
12596
|
+
...told.hint ? { hint: inWords(told.hint) } : {}
|
|
12597
|
+
});
|
|
12090
12598
|
return;
|
|
12091
12599
|
}
|
|
12092
12600
|
setDone(outcome.message ?? "sent");
|
|
12093
12601
|
onSubmitted?.(outcome.recordId ?? null);
|
|
12094
|
-
}, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview]);
|
|
12602
|
+
}, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
|
|
12095
12603
|
function advance() {
|
|
12096
12604
|
if (!oneAtATime) return;
|
|
12097
12605
|
if (index < live.length - 1) setAt(index + 1);
|
|
@@ -12150,7 +12658,22 @@ function FormStage({
|
|
|
12150
12658
|
] }) : null,
|
|
12151
12659
|
form.intro && index === 0 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { className: "text-sm text-muted-foreground", children: form.intro }) : null,
|
|
12152
12660
|
error ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalNotice, { error, className: "text-left" }) : null,
|
|
12153
|
-
|
|
12661
|
+
refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
12662
|
+
RefusalNotice,
|
|
12663
|
+
{
|
|
12664
|
+
error: refusal2,
|
|
12665
|
+
className: "text-left",
|
|
12666
|
+
onKeepEditing: () => {
|
|
12667
|
+
setRefusal(null);
|
|
12668
|
+
stage.current?.querySelector("input, textarea, select, button")?.focus();
|
|
12669
|
+
},
|
|
12670
|
+
onDiscard: () => {
|
|
12671
|
+
setRefusal(null);
|
|
12672
|
+
setAnswers({});
|
|
12673
|
+
setAt(0);
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
) : null,
|
|
12154
12677
|
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { ref: stage, className: "flex flex-col gap-4 text-left", children: [
|
|
12155
12678
|
(oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
12156
12679
|
Question,
|
|
@@ -12224,12 +12747,15 @@ function Question({
|
|
|
12224
12747
|
void upload?.(file).then((result) => {
|
|
12225
12748
|
if (!result) return;
|
|
12226
12749
|
if (result.ok) onChange(result.fileId);
|
|
12227
|
-
else
|
|
12750
|
+
else
|
|
12751
|
+
setUploadError(
|
|
12752
|
+
refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
|
|
12753
|
+
);
|
|
12228
12754
|
});
|
|
12229
12755
|
}
|
|
12230
12756
|
}
|
|
12231
12757
|
),
|
|
12232
|
-
uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
12758
|
+
uploadError ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(RefusalLine, { error: uploadError }) : null
|
|
12233
12759
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(FieldControl, { field, value, onChange, id })
|
|
12234
12760
|
] });
|
|
12235
12761
|
}
|
|
@@ -13016,10 +13542,15 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
13016
13542
|
onChange: (e) => setDraftBody(e.target.value)
|
|
13017
13543
|
}
|
|
13018
13544
|
),
|
|
13019
|
-
unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: "
|
|
13020
|
-
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { children: token.raw }),
|
|
13021
|
-
" \u2014
|
|
13022
|
-
|
|
13545
|
+
unresolved.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)("li", { className: "flex items-baseline gap-1", children: [
|
|
13546
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("code", { className: "text-destructive", children: token.raw }),
|
|
13547
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)("span", { "aria-hidden": "true", children: "\u2014" }),
|
|
13548
|
+
/* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
|
|
13549
|
+
RefusalLine,
|
|
13550
|
+
{
|
|
13551
|
+
error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
|
|
13552
|
+
}
|
|
13553
|
+
)
|
|
13023
13554
|
] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
|
|
13024
13555
|
] });
|
|
13025
13556
|
}
|
|
@@ -13224,7 +13755,21 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
13224
13755
|
signature.document_hash.slice(0, 12)
|
|
13225
13756
|
] })
|
|
13226
13757
|
] }),
|
|
13227
|
-
|
|
13758
|
+
intact === false ? (
|
|
13759
|
+
// THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
|
|
13760
|
+
// formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
|
|
13761
|
+
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
|
|
13762
|
+
RefusalLine,
|
|
13763
|
+
{
|
|
13764
|
+
className: "mt-0.5",
|
|
13765
|
+
error: refusal(
|
|
13766
|
+
"refused_by_rule",
|
|
13767
|
+
typeof verdict === "object" && verdict !== null && "reason" in verdict ? String(verdict.reason) : "The store says what is there no longer matches what was signed.",
|
|
13768
|
+
"Ask for it to be signed again on the version that is there now."
|
|
13769
|
+
)
|
|
13770
|
+
}
|
|
13771
|
+
)
|
|
13772
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: intact === true ? "The store says what was signed is still exactly what is there." : String(verdict ?? "") })
|
|
13228
13773
|
] }, signature.id);
|
|
13229
13774
|
}) }) : null,
|
|
13230
13775
|
!render ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("p", { className: "text-xs text-muted-foreground", children: 'Nothing has been frozen yet, so there is no version to sign. A signature seals one exact document version and its hash \u2014 "the document as it reads today" is not something anyone can agree to.' }) : signable.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
@@ -13485,14 +14030,14 @@ function NotifyRuleEditor({ tableId, seed, className }) {
|
|
|
13485
14030
|
var import_react95 = require("react");
|
|
13486
14031
|
var import_recharts = require("recharts");
|
|
13487
14032
|
var import_records11 = require("@ai-matrx/records");
|
|
13488
|
-
var
|
|
14033
|
+
var import_core8 = require("@ai-matrx/records/core");
|
|
13489
14034
|
var import_design_system46 = require("@ai-matrx/design-system");
|
|
13490
14035
|
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
13491
14036
|
function pretty(n) {
|
|
13492
14037
|
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
13493
14038
|
}
|
|
13494
|
-
function
|
|
13495
|
-
return (0,
|
|
14039
|
+
function asRefusal2(block) {
|
|
14040
|
+
return (0, import_core8.mapPgError)(
|
|
13496
14041
|
{
|
|
13497
14042
|
message: block.refused ?? "",
|
|
13498
14043
|
...block.sqlstate ? { code: block.sqlstate } : {}
|
|
@@ -13552,7 +14097,7 @@ function ChartBlock({ block, subject, className }) {
|
|
|
13552
14097
|
" ms"
|
|
13553
14098
|
] }) : null
|
|
13554
14099
|
] }),
|
|
13555
|
-
block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error:
|
|
14100
|
+
block.refused ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(RefusalNotice, { error: asRefusal2(block) }) : needs ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: needs }) : kind === "stuck" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(StuckList, { rows: block.rows ?? [] }) : points.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: "The store answered this question with no groups at all, so there is nothing to draw yet." }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(import_jsx_runtime49.Fragment, { children: kind === "number" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(BigNumber, { points, measure: primary, onDrill: drill }) : kind === "table" ? /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(GroupTable, { points, series, onDrill: drill }) : /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(import_jsx_runtime49.Fragment, { children: [
|
|
13556
14101
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Drawing, { kind, points, series, config, onDrill: drill }),
|
|
13557
14102
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Values, { points, measure: primary, config, onDrill: drill })
|
|
13558
14103
|
] }) })
|
|
@@ -14118,13 +14663,14 @@ var import_react99 = require("@ai-matrx/records/react");
|
|
|
14118
14663
|
var import_records12 = require("@ai-matrx/records");
|
|
14119
14664
|
var import_design_system48 = require("@ai-matrx/design-system");
|
|
14120
14665
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
14666
|
+
var FORM_NOT_HERE_LINE = "The link named a form this table does not have \u2014 it may have been removed, or it belongs to another table. The forms this table does have are listed here.";
|
|
14121
14667
|
var WHAT_A_FORM_IS = "A form asks for this table's own fields, and its answers land here as ordinary records stamped with the form they came through.";
|
|
14122
14668
|
var NO_ADMIN = "This table has no forms. Making one needs the admin level on it, because a form decides what people with no account may add here.";
|
|
14123
14669
|
function formSuggestion(tableName2) {
|
|
14124
14670
|
const subject = tableName2?.trim() ? tableName2.trim() : "this table";
|
|
14125
14671
|
return `Make me a form that collects new ${subject} entries and tells me when somebody answers.`;
|
|
14126
14672
|
}
|
|
14127
|
-
function FormsPanel({ tableId, className }) {
|
|
14673
|
+
function FormsPanel({ tableId, activeFormId, className }) {
|
|
14128
14674
|
const client = (0, import_react99.useRecordsClient)();
|
|
14129
14675
|
const publishGate = usePublishGate();
|
|
14130
14676
|
const host = useRecordsUi();
|
|
@@ -14135,6 +14681,11 @@ function FormsPanel({ tableId, className }) {
|
|
|
14135
14681
|
const [busy, setBusy] = (0, import_react98.useState)(null);
|
|
14136
14682
|
const [copied, setCopied] = (0, import_react98.useState)(null);
|
|
14137
14683
|
const [building, setBuilding] = (0, import_react98.useState)(false);
|
|
14684
|
+
const linked = activeFormId && forms !== null ? forms.find((f) => f.form_id === activeFormId) ?? null : null;
|
|
14685
|
+
const linkedMissing = Boolean(activeFormId) && forms !== null && linked === null;
|
|
14686
|
+
(0, import_react98.useEffect)(() => {
|
|
14687
|
+
if (linked && rights.structure) setBuilding(true);
|
|
14688
|
+
}, [linked, rights.structure]);
|
|
14138
14689
|
const load = (0, import_react98.useCallback)(async () => {
|
|
14139
14690
|
const answered = await client.forms({ table_id: tableId });
|
|
14140
14691
|
if (!answered.ok) {
|
|
@@ -14183,10 +14734,12 @@ function FormsPanel({ tableId, className }) {
|
|
|
14183
14734
|
rights.structure && forms.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Button, { size: "sm", variant: building ? "secondary" : "ghost", onClick: () => setBuilding((b) => !b), children: building ? "Done building" : "Build a form" }) : null
|
|
14184
14735
|
] }),
|
|
14185
14736
|
error ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(RefusalNotice, { error }) : null,
|
|
14737
|
+
linkedMissing ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: FORM_NOT_HERE_LINE }) : null,
|
|
14186
14738
|
building ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
14187
14739
|
FormBuilder,
|
|
14188
14740
|
{
|
|
14189
14741
|
tableId,
|
|
14742
|
+
activeFormId: linked ? linked.form_id : null,
|
|
14190
14743
|
onActiveForm: () => {
|
|
14191
14744
|
void load();
|
|
14192
14745
|
}
|
|
@@ -14212,50 +14765,62 @@ function FormsPanel({ tableId, className }) {
|
|
|
14212
14765
|
) : null,
|
|
14213
14766
|
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
|
|
14214
14767
|
const url = `${origin}${(0, import_records12.publicFormPath)(form.form_id)}`;
|
|
14215
|
-
return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
"
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
"
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(
|
|
14769
|
+
"li",
|
|
14770
|
+
{
|
|
14771
|
+
"data-linked": form.form_id === activeFormId ? "true" : void 0,
|
|
14772
|
+
ref: form.form_id === activeFormId ? (el) => el?.scrollIntoView({ block: "nearest" }) : void 0,
|
|
14773
|
+
className: (0, import_design_system48.cn)(
|
|
14774
|
+
"rounded-md border p-2.5",
|
|
14775
|
+
form.form_id === activeFormId && "border-primary ring-1 ring-primary"
|
|
14776
|
+
),
|
|
14777
|
+
children: [
|
|
14778
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
14779
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
|
|
14780
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Badge, { variant: form.state === "open" ? "default" : "secondary", children: form.state })
|
|
14781
|
+
] }),
|
|
14782
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-0.5 text-xs text-muted-foreground", children: FORM_STATE_WORDS[form.state] }),
|
|
14783
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 text-xs", children: [
|
|
14784
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.in_table }),
|
|
14785
|
+
" in the table",
|
|
14786
|
+
form.held > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
|
|
14787
|
+
" \xB7 ",
|
|
14788
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.held }),
|
|
14789
|
+
" waiting for someone"
|
|
14790
|
+
] }) : null,
|
|
14791
|
+
form.rejected > 0 ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(import_jsx_runtime51.Fragment, { children: [
|
|
14792
|
+
" \xB7 ",
|
|
14793
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "font-medium", children: form.rejected }),
|
|
14794
|
+
" turned away"
|
|
14795
|
+
] }) : null,
|
|
14796
|
+
form.submission_cap !== null ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
14797
|
+
" \xB7 stops at ",
|
|
14798
|
+
form.submission_cap
|
|
14799
|
+
] }) : null
|
|
14800
|
+
] }),
|
|
14801
|
+
/* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
|
|
14802
|
+
form.published_at ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(import_design_system48.Button, { size: "sm", variant: "outline", onClick: () => void copy(url, form.form_id), children: copied === form.form_id ? "Copied" : "Copy link" }) : null,
|
|
14803
|
+
rights.structure && !publishGate.blocked ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(
|
|
14804
|
+
import_design_system48.Button,
|
|
14805
|
+
{
|
|
14806
|
+
size: "sm",
|
|
14807
|
+
variant: "ghost",
|
|
14808
|
+
disabled: busy === form.form_id,
|
|
14809
|
+
onClick: () => void toggle(form),
|
|
14810
|
+
children: busy === form.form_id ? "\u2026" : form.published_at && !form.closed_at ? "Unpublish" : "Publish"
|
|
14811
|
+
}
|
|
14812
|
+
) : null
|
|
14813
|
+
] }),
|
|
14814
|
+
rights.structure && publishGate.why ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "mt-1.5 text-xs text-muted-foreground", children: publishGate.why }) : null,
|
|
14815
|
+
shown && shown === url ? /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)("p", { className: "mt-1.5 break-all rounded border border-dashed px-2 py-1 text-xs", children: [
|
|
14816
|
+
"This browser would not let the page copy for you, so here it is to copy by hand:",
|
|
14817
|
+
" ",
|
|
14818
|
+
url
|
|
14819
|
+
] }) : null
|
|
14820
|
+
]
|
|
14821
|
+
},
|
|
14822
|
+
form.form_id
|
|
14823
|
+
);
|
|
14259
14824
|
}) }),
|
|
14260
14825
|
!rights.known ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("p", { className: "text-xs text-muted-foreground", children: "Still asking what you may do with this table \u2014 nothing is offered until it answers." }) : null
|
|
14261
14826
|
] });
|
|
@@ -14912,7 +15477,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
14912
15477
|
void client.captureOpen({ sheet_id: sheetId }).then((res) => {
|
|
14913
15478
|
if (cancelled) return;
|
|
14914
15479
|
if (res.ok) setFace(res.data);
|
|
14915
|
-
else setLoadFailed(res.error
|
|
15480
|
+
else setLoadFailed(refusalOr(res.error, "unreachable", "This sheet could not be opened."));
|
|
14916
15481
|
});
|
|
14917
15482
|
return () => {
|
|
14918
15483
|
cancelled = true;
|
|
@@ -14959,7 +15524,11 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
14959
15524
|
const needed = questions.filter((x) => x.required && !answered(x.field));
|
|
14960
15525
|
if (needed.length > 0) {
|
|
14961
15526
|
setMissing(
|
|
14962
|
-
|
|
15527
|
+
refusal(
|
|
15528
|
+
"invalid_argument",
|
|
15529
|
+
`${needed.map((x) => (x.ask ?? x.field).replace(/\?$/, "")).join(", ")} still ${needed.length === 1 ? "needs" : "need"} an answer.`,
|
|
15530
|
+
"Answer it, then capture again. Nothing has been queued yet."
|
|
15531
|
+
)
|
|
14963
15532
|
);
|
|
14964
15533
|
setAt(questions.findIndex((x) => x.field === needed[0].field));
|
|
14965
15534
|
return;
|
|
@@ -14990,7 +15559,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
14990
15559
|
if (online) await sync();
|
|
14991
15560
|
}
|
|
14992
15561
|
if (loadFailed) {
|
|
14993
|
-
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("section", { className: (0, import_design_system51.cn)("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("
|
|
15562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("section", { className: (0, import_design_system51.cn)("mx-auto w-full max-w-sm p-4", className), children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { "data-testid": "capture-refusal", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(RefusalNotice, { error: loadFailed, className: "text-sm" }) }) });
|
|
14994
15563
|
}
|
|
14995
15564
|
if (face === void 0) {
|
|
14996
15565
|
return /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(import_design_system51.Skeleton, { className: (0, import_design_system51.cn)("mx-auto h-64 w-full max-w-sm", className) });
|
|
@@ -15021,7 +15590,21 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
15021
15590
|
] });
|
|
15022
15591
|
const queuePanel = items.filter((i) => i.state !== "landed").length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("ul", { className: "flex flex-col gap-1 rounded border p-2", "data-testid": "capture-queue", children: items.filter((i) => i.state !== "landed").map((i) => /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("li", { className: "flex items-start gap-2 text-[11px]", children: [
|
|
15023
15592
|
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "tabular-nums text-muted-foreground", children: new Date(i.captured_at).toLocaleTimeString() }),
|
|
15024
|
-
|
|
15593
|
+
i.state === "refused" ? (
|
|
15594
|
+
// THE STORE REFUSED THIS ONE: a refusal line, heading and remedy
|
|
15595
|
+
// both — never its raw words in red (lane REFUSAL-SWEEP).
|
|
15596
|
+
/* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
15597
|
+
RefusalLine,
|
|
15598
|
+
{
|
|
15599
|
+
className: "flex-1",
|
|
15600
|
+
error: refusal(
|
|
15601
|
+
"refused_by_rule",
|
|
15602
|
+
i.last_error ?? "This one was refused.",
|
|
15603
|
+
"Throw it away and capture it again with the answer changed."
|
|
15604
|
+
)
|
|
15605
|
+
}
|
|
15606
|
+
)
|
|
15607
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("span", { className: "flex-1", children: i.last_error ?? "Waiting for a signal." }),
|
|
15025
15608
|
i.state === "refused" ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
15026
15609
|
import_design_system51.Button,
|
|
15027
15610
|
{
|
|
@@ -15141,7 +15724,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
15141
15724
|
}
|
|
15142
15725
|
}
|
|
15143
15726
|
),
|
|
15144
|
-
missing ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("
|
|
15727
|
+
missing ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)("div", { "data-testid": "capture-missing", children: /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(RefusalNotice, { error: missing, className: "text-sm" }) }) : null,
|
|
15145
15728
|
/* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
15146
15729
|
at > 0 ? /* @__PURE__ */ (0, import_jsx_runtime54.jsx)(
|
|
15147
15730
|
import_design_system51.Button,
|
|
@@ -15369,13 +15952,13 @@ function AdHocCaptureSheet({
|
|
|
15369
15952
|
setUploadError(null);
|
|
15370
15953
|
void host.upload?.(file).then((result) => {
|
|
15371
15954
|
if (result.ok) setFileId(result.fileId);
|
|
15372
|
-
else setUploadError(result.reason);
|
|
15955
|
+
else setUploadError(refusal("invalid_argument", result.reason, "Pick another file, or try this one again."));
|
|
15373
15956
|
});
|
|
15374
15957
|
}
|
|
15375
15958
|
}
|
|
15376
15959
|
),
|
|
15377
15960
|
fileId ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Attached." }) : null,
|
|
15378
|
-
uploadError ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
15961
|
+
uploadError ? /* @__PURE__ */ (0, import_jsx_runtime55.jsx)(RefusalLine, { error: uploadError }) : null
|
|
15379
15962
|
] }) : null,
|
|
15380
15963
|
/* @__PURE__ */ (0, import_jsx_runtime55.jsx)(
|
|
15381
15964
|
import_design_system52.Textarea,
|
|
@@ -15838,17 +16421,17 @@ async function declareTable(client, spec) {
|
|
|
15838
16421
|
if (!written.ok) return undo(client, table.data, home.data, spec.name, written.error);
|
|
15839
16422
|
return { ok: true, data: table.data, homeId: home.data };
|
|
15840
16423
|
}
|
|
15841
|
-
async function undo(client, tableId, homeId, name,
|
|
16424
|
+
async function undo(client, tableId, homeId, name, refusal2) {
|
|
15842
16425
|
const removed = await client.recordDelete({ record_id: tableId });
|
|
15843
16426
|
if (removed.ok) {
|
|
15844
16427
|
await client.recordDelete({ record_id: homeId });
|
|
15845
|
-
return { ok: false, error:
|
|
16428
|
+
return { ok: false, error: refusal2 };
|
|
15846
16429
|
}
|
|
15847
16430
|
return {
|
|
15848
16431
|
ok: false,
|
|
15849
16432
|
error: {
|
|
15850
|
-
...
|
|
15851
|
-
hint: `${
|
|
16433
|
+
...refusal2,
|
|
16434
|
+
hint: `${refusal2.hint ? `${refusal2.hint} ` : ""}"${name}" was created before this happened and could not be removed again, so it is in your tables with no columns \u2014 open it and add the first column, or delete it from its own Settings.`
|
|
15852
16435
|
}
|
|
15853
16436
|
};
|
|
15854
16437
|
}
|
|
@@ -15901,13 +16484,31 @@ var LANE_EMPTY = {
|
|
|
15901
16484
|
community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
|
|
15902
16485
|
app: "The app has not needed a table of its own in this organization yet. Saving a view, writing a comment or building a form makes one, and it appears here rather than among your own data."
|
|
15903
16486
|
};
|
|
16487
|
+
function keptByTheApp(table) {
|
|
16488
|
+
return table.kept_by_the_app === true;
|
|
16489
|
+
}
|
|
15904
16490
|
function laneFor(table) {
|
|
15905
16491
|
if (table.is_kernel) return "system";
|
|
15906
|
-
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX)) return "app";
|
|
16492
|
+
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return "app";
|
|
15907
16493
|
if (table.visibility === "public" || table.visibility === "link") return "community";
|
|
15908
16494
|
if (table.visibility === "personal") return "mine";
|
|
15909
16495
|
return "organization";
|
|
15910
16496
|
}
|
|
16497
|
+
var VISIBILITY_LANES = ["mine", "organization", "community", "world"];
|
|
16498
|
+
var VISIBILITY_LANE_TITLE = {
|
|
16499
|
+
mine: "Mine",
|
|
16500
|
+
organization: "My organization",
|
|
16501
|
+
community: "Community",
|
|
16502
|
+
world: "World"
|
|
16503
|
+
};
|
|
16504
|
+
function visibilityLaneFor(table) {
|
|
16505
|
+
if (table.is_kernel) return null;
|
|
16506
|
+
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
|
|
16507
|
+
if (table.visibility === "personal") return "mine";
|
|
16508
|
+
if (table.visibility === "link") return "community";
|
|
16509
|
+
if (table.visibility === "public") return "world";
|
|
16510
|
+
return "organization";
|
|
16511
|
+
}
|
|
15911
16512
|
function TablesHome({ onOpenTable, className }) {
|
|
15912
16513
|
const client = (0, import_react117.useRecordsClient)();
|
|
15913
16514
|
const tables = (0, import_react117.useTables)();
|
|
@@ -16055,6 +16656,43 @@ var import_react118 = require("react");
|
|
|
16055
16656
|
var import_react119 = require("@ai-matrx/records/react");
|
|
16056
16657
|
var import_design_system57 = require("@ai-matrx/design-system");
|
|
16057
16658
|
var import_jsx_runtime61 = require("react/jsx-runtime");
|
|
16659
|
+
var RAIL_TITLE = {
|
|
16660
|
+
settings: "Table settings",
|
|
16661
|
+
inbox: "Inbox",
|
|
16662
|
+
import: "Import",
|
|
16663
|
+
field: "Add a field",
|
|
16664
|
+
record: "Record",
|
|
16665
|
+
"new-record": "New record",
|
|
16666
|
+
forms: "Forms",
|
|
16667
|
+
bookings: "Bookings",
|
|
16668
|
+
checklists: "Checklists",
|
|
16669
|
+
notifications: "Notifications",
|
|
16670
|
+
portals: "Portals",
|
|
16671
|
+
"who-changed": "Who changed this"
|
|
16672
|
+
};
|
|
16673
|
+
function RailFrame({
|
|
16674
|
+
phone,
|
|
16675
|
+
title,
|
|
16676
|
+
onClose,
|
|
16677
|
+
children
|
|
16678
|
+
}) {
|
|
16679
|
+
if (phone) {
|
|
16680
|
+
return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
16681
|
+
import_design_system57.BottomSheet,
|
|
16682
|
+
{
|
|
16683
|
+
open: true,
|
|
16684
|
+
onOpenChange: (open) => {
|
|
16685
|
+
if (!open) onClose();
|
|
16686
|
+
},
|
|
16687
|
+
title,
|
|
16688
|
+
size: "full",
|
|
16689
|
+
surface: "solid",
|
|
16690
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { "data-rail-sheet": "", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6", children })
|
|
16691
|
+
}
|
|
16692
|
+
);
|
|
16693
|
+
}
|
|
16694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
|
|
16695
|
+
}
|
|
16058
16696
|
function filterInWords(filter, fields) {
|
|
16059
16697
|
const nameOf = (key) => {
|
|
16060
16698
|
const field = fields.find((f) => f.key === key);
|
|
@@ -16102,9 +16740,43 @@ function chooseSurface(current, pressed) {
|
|
|
16102
16740
|
function surfaceChosen(current, asking) {
|
|
16103
16741
|
return asking.main !== void 0 ? current.main === asking.main : current.rail === asking.rail;
|
|
16104
16742
|
}
|
|
16105
|
-
function openingRail(activeRecordId) {
|
|
16106
|
-
|
|
16743
|
+
function openingRail(activeRecordId, activeRail) {
|
|
16744
|
+
if (activeRecordId) return { rail: "record", record: activeRecordId };
|
|
16745
|
+
const named = railFromParam(activeRail);
|
|
16746
|
+
if (named !== null && named !== "share") return { rail: named, record: null };
|
|
16747
|
+
return { rail: "none", record: null };
|
|
16748
|
+
}
|
|
16749
|
+
var RAIL_SPELLINGS = {
|
|
16750
|
+
forms: "forms",
|
|
16751
|
+
form: "forms",
|
|
16752
|
+
bookings: "bookings",
|
|
16753
|
+
booking: "bookings",
|
|
16754
|
+
checklists: "checklists",
|
|
16755
|
+
checklist: "checklists",
|
|
16756
|
+
notifications: "notifications",
|
|
16757
|
+
notification: "notifications",
|
|
16758
|
+
digests: "notifications",
|
|
16759
|
+
digest: "notifications",
|
|
16760
|
+
subscriptions: "notifications",
|
|
16761
|
+
subscription: "notifications",
|
|
16762
|
+
portals: "portals",
|
|
16763
|
+
portal: "portals",
|
|
16764
|
+
settings: "settings",
|
|
16765
|
+
import: "import",
|
|
16766
|
+
inbox: "inbox",
|
|
16767
|
+
share: "share",
|
|
16768
|
+
sharing: "share"
|
|
16769
|
+
};
|
|
16770
|
+
function railFromParam(raw) {
|
|
16771
|
+
if (typeof raw !== "string") return null;
|
|
16772
|
+
const token = raw.trim().toLowerCase();
|
|
16773
|
+
if (token === "") return null;
|
|
16774
|
+
return RAIL_SPELLINGS[token] ?? null;
|
|
16775
|
+
}
|
|
16776
|
+
function unknownRailLine(raw) {
|
|
16777
|
+
return `The link asked to open \u201C${raw}\u201D on this table, which is not something a table has, so you are looking at its records. A table opens Forms, Bookings, Checklists, Notifications, Portals, Settings, Import, Inbox and Share \u2014 each is a button above.`;
|
|
16107
16778
|
}
|
|
16779
|
+
var SHARE_NOT_YOURS_LINE = "The link asked to open this table's sharing, and only someone with Admin on this table can share it, so it did not open. Ask an admin of this table to share it or to give you Admin.";
|
|
16108
16780
|
function openingView(activeView, activeDashboardId) {
|
|
16109
16781
|
const named = pageViewFromParam(activeView);
|
|
16110
16782
|
if (named === null) {
|
|
@@ -16132,6 +16804,8 @@ function TablePage({
|
|
|
16132
16804
|
activeGroupField,
|
|
16133
16805
|
cameFrom,
|
|
16134
16806
|
filter,
|
|
16807
|
+
activeRail,
|
|
16808
|
+
activeItemId,
|
|
16135
16809
|
className
|
|
16136
16810
|
}) {
|
|
16137
16811
|
const client = (0, import_react119.useRecordsClient)();
|
|
@@ -16141,8 +16815,13 @@ function TablePage({
|
|
|
16141
16815
|
const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
|
|
16142
16816
|
const organizationId = (0, import_react119.useRecordsClient)().config.organizationId;
|
|
16143
16817
|
const [view, setView] = (0, import_react118.useState)(null);
|
|
16144
|
-
const opening = openingRail(activeRecordId);
|
|
16818
|
+
const opening = openingRail(activeRecordId, activeRail);
|
|
16145
16819
|
const opened = openingView(activeView, activeDashboardId);
|
|
16820
|
+
const railAsked = typeof activeRail === "string" ? activeRail.trim() : "";
|
|
16821
|
+
const railUnknown = railAsked !== "" && railFromParam(railAsked) === null ? railAsked : null;
|
|
16822
|
+
const shareFromLink = railFromParam(activeRail) === "share";
|
|
16823
|
+
const canShare = useCanShare();
|
|
16824
|
+
const itemFor = (which) => railFromParam(activeRail) === which ? activeItemId ?? null : null;
|
|
16146
16825
|
const [asking, setAsking] = (0, import_react118.useState)(null);
|
|
16147
16826
|
const [layoutFromLink, setLayoutFromLink] = (0, import_react118.useState)(opened.layout);
|
|
16148
16827
|
const [surface, setSurface] = (0, import_react118.useState)({
|
|
@@ -16153,6 +16832,7 @@ function TablePage({
|
|
|
16153
16832
|
const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
|
|
16154
16833
|
const [openRecord, setOpenRecord] = (0, import_react118.useState)(opening.record);
|
|
16155
16834
|
const viewVersion = useRecordVersion(view?.id ?? null);
|
|
16835
|
+
const phone = (0, import_design_system57.useIsMobile)();
|
|
16156
16836
|
const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
|
|
16157
16837
|
const press = (pressed) => {
|
|
16158
16838
|
const next = chooseSurface(surface, pressed);
|
|
@@ -16167,6 +16847,12 @@ function TablePage({
|
|
|
16167
16847
|
setOpenRecord(activeRecordId);
|
|
16168
16848
|
setSurface((now) => ({ ...now, rail: "record" }));
|
|
16169
16849
|
}, [activeRecordId]);
|
|
16850
|
+
(0, import_react118.useEffect)(() => {
|
|
16851
|
+
if (activeRecordId) return;
|
|
16852
|
+
const named = railFromParam(activeRail);
|
|
16853
|
+
if (named === null || named === "share") return;
|
|
16854
|
+
setSurface((now) => ({ ...now, rail: named }));
|
|
16855
|
+
}, [activeRail, activeRecordId]);
|
|
16170
16856
|
(0, import_react118.useEffect)(() => {
|
|
16171
16857
|
const named = pageViewFromParam(activeView);
|
|
16172
16858
|
if (named === null) return;
|
|
@@ -16174,6 +16860,7 @@ function TablePage({
|
|
|
16174
16860
|
setLayoutFromLink(isLayout ? named : null);
|
|
16175
16861
|
setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
|
|
16176
16862
|
}, [activeView]);
|
|
16863
|
+
const [viewSaveRefused, setViewSaveRefused] = (0, import_react118.useState)(null);
|
|
16177
16864
|
const patchView = (0, import_react118.useCallback)(
|
|
16178
16865
|
async (patch) => {
|
|
16179
16866
|
const current = view;
|
|
@@ -16181,12 +16868,13 @@ function TablePage({
|
|
|
16181
16868
|
setView({ ...current, ...patch });
|
|
16182
16869
|
const document2 = viewPatchDocument(patch);
|
|
16183
16870
|
if (Object.keys(document2).length === 0) return;
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16871
|
+
setViewSaveRefused(null);
|
|
16872
|
+
const written = await saveViewPatch(client, current.id, document2, viewVersion.version);
|
|
16873
|
+
if (written.ok) {
|
|
16874
|
+
viewVersion.note(written.data);
|
|
16875
|
+
return;
|
|
16876
|
+
}
|
|
16877
|
+
setViewSaveRefused({ error: written.error, patch, before: current });
|
|
16190
16878
|
},
|
|
16191
16879
|
[client, view, viewVersion]
|
|
16192
16880
|
);
|
|
@@ -16331,11 +17019,33 @@ function TablePage({
|
|
|
16331
17019
|
organizationId,
|
|
16332
17020
|
subjectId: tableId,
|
|
16333
17021
|
name: table.data?.name,
|
|
16334
|
-
may: rights.share
|
|
17022
|
+
may: rights.share,
|
|
17023
|
+
initiallyOpen: shareFromLink
|
|
16335
17024
|
}
|
|
16336
17025
|
),
|
|
16337
17026
|
/* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ExportMenu, { tableId })
|
|
16338
17027
|
] }),
|
|
17028
|
+
viewSaveRefused ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
17029
|
+
RefusalNotice,
|
|
17030
|
+
{
|
|
17031
|
+
error: viewSaveRefused.error,
|
|
17032
|
+
actions: /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)("span", { className: "flex flex-wrap gap-1 pt-0.5", "data-view-save-refused": "", children: [
|
|
17033
|
+
/* @__PURE__ */ (0, import_jsx_runtime61.jsx)(import_design_system57.Button, { size: "sm", variant: "outline", onClick: () => void patchView(viewSaveRefused.patch), children: "Try again" }),
|
|
17034
|
+
/* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
17035
|
+
import_design_system57.Button,
|
|
17036
|
+
{
|
|
17037
|
+
size: "sm",
|
|
17038
|
+
variant: "ghost",
|
|
17039
|
+
onClick: () => {
|
|
17040
|
+
setView(viewSaveRefused.before);
|
|
17041
|
+
setViewSaveRefused(null);
|
|
17042
|
+
},
|
|
17043
|
+
children: "Discard"
|
|
17044
|
+
}
|
|
17045
|
+
)
|
|
17046
|
+
] })
|
|
17047
|
+
}
|
|
17048
|
+
) : null,
|
|
16339
17049
|
main === "dashboards" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : main === "archived" ? (
|
|
16340
17050
|
/* THE ROWS THAT LEFT. Restoring one puts it back in the grid, so the
|
|
16341
17051
|
page returns to the records rather than leaving the person looking
|
|
@@ -16354,6 +17064,8 @@ function TablePage({
|
|
|
16354
17064
|
}
|
|
16355
17065
|
)
|
|
16356
17066
|
) : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(import_jsx_runtime61.Fragment, { children: [
|
|
17067
|
+
railUnknown ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownRailLine(railUnknown) }) : null,
|
|
17068
|
+
shareFromLink && rights.known && (!rights.share || !canShare) ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: canShare ? SHARE_NOT_YOURS_LINE : shareUnavailableReason() }) : null,
|
|
16357
17069
|
opened.unknown ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "rounded-md border border-amber-600/40 bg-amber-500/5 px-3 py-2 text-xs leading-relaxed text-amber-700 dark:border-amber-400/40 dark:text-amber-300", children: unknownViewLine(opened.unknown) }) : null,
|
|
16358
17070
|
cameFrom ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
16359
17071
|
"p",
|
|
@@ -16394,7 +17106,7 @@ function TablePage({
|
|
|
16394
17106
|
view ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
|
|
16395
17107
|
] })
|
|
16396
17108
|
] }),
|
|
16397
|
-
rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(
|
|
17109
|
+
rail === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime61.jsxs)(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
|
|
16398
17110
|
rail === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
16399
17111
|
TableSettings,
|
|
16400
17112
|
{
|
|
@@ -16412,7 +17124,7 @@ function TablePage({
|
|
|
16412
17124
|
}
|
|
16413
17125
|
}
|
|
16414
17126
|
) : null,
|
|
16415
|
-
rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId }) : null,
|
|
17127
|
+
rail === "forms" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FormsPanel, { tableId, activeFormId: itemFor("forms") }) : null,
|
|
16416
17128
|
rail === "bookings" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(BookingSlots, { tableId }) : null,
|
|
16417
17129
|
rail === "checklists" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|
|
16418
17130
|
ChecklistsPanel,
|
|
@@ -16424,8 +17136,8 @@ function TablePage({
|
|
|
16424
17136
|
}
|
|
16425
17137
|
}
|
|
16426
17138
|
) : null,
|
|
16427
|
-
rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId }) : null,
|
|
16428
|
-
rail === "portals" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(PortalsPanel, { tableId }) : null,
|
|
17139
|
+
rail === "notifications" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(SubscriptionsPanel, { tableId, activeRuleId: itemFor("notifications") }) : null,
|
|
17140
|
+
rail === "portals" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(PortalsPanel, { tableId, activePortalId: itemFor("portals") }) : null,
|
|
16429
17141
|
rail === "import" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(ImportWizard, { tableId, onDone: () => setRail("none") }) : null,
|
|
16430
17142
|
rail === "field" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(FieldEditor, { tableId, onSaved: () => setRail("none"), onCancel: () => setRail("none") }) : null,
|
|
16431
17143
|
rail === "new-record" ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)(
|