@ai-matrx/records-ui 0.82.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 +22 -0
- package/dist/index.cjs +279 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -4
- package/dist/index.d.ts +67 -4
- package/dist/index.js +278 -45
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2114,7 +2114,7 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2114
2114
|
startedFrom.current = null;
|
|
2115
2115
|
}, []);
|
|
2116
2116
|
const writeCellNow = useCallback4(
|
|
2117
|
-
async (rowId, key, value) => {
|
|
2117
|
+
async (rowId, key, value, resent = false) => {
|
|
2118
2118
|
const id = cellKey(rowId, key);
|
|
2119
2119
|
setOptimistic((held) => ({ ...held, [id]: value }));
|
|
2120
2120
|
setSaving((held) => ({ ...held, [id]: true }));
|
|
@@ -2157,16 +2157,25 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2157
2157
|
delete next[id];
|
|
2158
2158
|
return next;
|
|
2159
2159
|
});
|
|
2160
|
-
|
|
2160
|
+
const conflict = asWriteConflict(written.error, rowId);
|
|
2161
|
+
if (written.error.code === "stale_write") {
|
|
2162
|
+
if (conflict) rememberVersion(versions.current, rowId, conflict.current_version);
|
|
2163
|
+
else versions.current.delete(rowId);
|
|
2164
|
+
if (conflict && !resent && !Object.prototype.hasOwnProperty.call(conflict.contested_fields, key)) {
|
|
2165
|
+
await writeCellNow(rowId, key, value, true);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2161
2169
|
setRefusals((held) => ({
|
|
2162
2170
|
...held,
|
|
2163
2171
|
[id]: {
|
|
2164
2172
|
error: written.error,
|
|
2165
|
-
conflict
|
|
2173
|
+
conflict,
|
|
2166
2174
|
attempted: value
|
|
2167
2175
|
}
|
|
2168
2176
|
}));
|
|
2169
2177
|
},
|
|
2178
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2170
2179
|
[client, reload]
|
|
2171
2180
|
);
|
|
2172
2181
|
const writeCell = useCallback4(
|
|
@@ -2241,6 +2250,33 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2241
2250
|
},
|
|
2242
2251
|
[reload]
|
|
2243
2252
|
);
|
|
2253
|
+
const keepEditing = useCallback4(
|
|
2254
|
+
(rowId, key) => {
|
|
2255
|
+
const held = refusals[cellKey(rowId, key)];
|
|
2256
|
+
if (!held || !writable(rowId)) return;
|
|
2257
|
+
startedFrom.current = shownValue(rowId, key);
|
|
2258
|
+
typed.current = held.attempted;
|
|
2259
|
+
setDraft(held.attempted);
|
|
2260
|
+
setEditing({ rowId, key });
|
|
2261
|
+
},
|
|
2262
|
+
[refusals, writable, shownValue]
|
|
2263
|
+
);
|
|
2264
|
+
const discard = useCallback4((rowId, key) => {
|
|
2265
|
+
const id = cellKey(rowId, key);
|
|
2266
|
+
setRefusals((held) => {
|
|
2267
|
+
if (!held[id]) return held;
|
|
2268
|
+
const next = { ...held };
|
|
2269
|
+
delete next[id];
|
|
2270
|
+
return next;
|
|
2271
|
+
});
|
|
2272
|
+
setOptimistic((held) => {
|
|
2273
|
+
if (!(id in held)) return held;
|
|
2274
|
+
const next = { ...held };
|
|
2275
|
+
delete next[id];
|
|
2276
|
+
return next;
|
|
2277
|
+
});
|
|
2278
|
+
setEditing((open) => open && open.rowId === rowId && open.key === key ? null : open);
|
|
2279
|
+
}, []);
|
|
2244
2280
|
const remove = useCallback4(
|
|
2245
2281
|
async (rowId) => {
|
|
2246
2282
|
setBusy(true);
|
|
@@ -2292,6 +2328,8 @@ function useGridEditing({ tableId, fields, rows, reload, canWrite, mayWriteRow }
|
|
|
2292
2328
|
},
|
|
2293
2329
|
retry,
|
|
2294
2330
|
keepTheirs,
|
|
2331
|
+
keepEditing,
|
|
2332
|
+
discard,
|
|
2295
2333
|
remove,
|
|
2296
2334
|
addRecord,
|
|
2297
2335
|
rowError,
|
|
@@ -2360,13 +2398,28 @@ function GridCell({
|
|
|
2360
2398
|
draft: editing.draft,
|
|
2361
2399
|
onType: editing.type,
|
|
2362
2400
|
onCommit: (move) => editing.commit(move),
|
|
2363
|
-
onCancel: editing.cancel
|
|
2401
|
+
onCancel: editing.cancel,
|
|
2402
|
+
below: (
|
|
2403
|
+
// KEPT EDITING: the refusal stays beside the value it is about, so the
|
|
2404
|
+
// person reads what to change while changing it. Keep editing is the
|
|
2405
|
+
// state they are in, so only Discard is offered. It is INSIDE the open
|
|
2406
|
+
// cell, so pressing Discard is not "focus left the cell" — which would
|
|
2407
|
+
// commit the refused value one more time on its way out.
|
|
2408
|
+
refusal2 && !refusal2.conflict ? /* @__PURE__ */ jsx10(
|
|
2409
|
+
RefusalNotice,
|
|
2410
|
+
{
|
|
2411
|
+
error: refusal2.error,
|
|
2412
|
+
className: "py-1.5",
|
|
2413
|
+
onDiscard: () => editing.discard(row.id, field.key)
|
|
2414
|
+
}
|
|
2415
|
+
) : null
|
|
2416
|
+
)
|
|
2364
2417
|
}
|
|
2365
2418
|
);
|
|
2366
2419
|
}
|
|
2367
2420
|
if (editorKindFor(field) === "checkbox") {
|
|
2368
2421
|
const answered = value === true || value === false;
|
|
2369
|
-
return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full items-center gap-1.5", children: [
|
|
2422
|
+
return /* @__PURE__ */ jsxs6("span", { "data-matrx-cell-control": "", className: "flex w-full flex-wrap items-center gap-1.5", children: [
|
|
2370
2423
|
/* @__PURE__ */ jsx10(
|
|
2371
2424
|
Checkbox3,
|
|
2372
2425
|
{
|
|
@@ -2379,10 +2432,7 @@ function GridCell({
|
|
|
2379
2432
|
}
|
|
2380
2433
|
),
|
|
2381
2434
|
answered ? null : /* @__PURE__ */ jsx10("span", { className: "text-xs text-muted-foreground", title: "nobody has answered this yet", children: "\u2014" }),
|
|
2382
|
-
refusal2 ? /* @__PURE__ */
|
|
2383
|
-
/* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
|
|
2384
|
-
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" })
|
|
2385
|
-
] }) : null
|
|
2435
|
+
refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: false }) : null
|
|
2386
2436
|
] });
|
|
2387
2437
|
}
|
|
2388
2438
|
const openThisCell = (event) => {
|
|
@@ -2455,22 +2505,48 @@ function GridCell({
|
|
|
2455
2505
|
),
|
|
2456
2506
|
onAskWhoChanged ? /* @__PURE__ */ jsx10(WhoBadge, { field, row, onAskWhoChanged }) : null,
|
|
2457
2507
|
state === "saving" ? /* @__PURE__ */ jsx10("span", { className: "px-1 text-[10px] text-muted-foreground", children: "Saving\u2026" }) : null,
|
|
2458
|
-
refusal2 ? /* @__PURE__ */
|
|
2459
|
-
/* @__PURE__ */ jsx10(RefusalLine, { error: refusal2.error }),
|
|
2460
|
-
refusal2.conflict ? /* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
|
|
2461
|
-
"Theirs: ",
|
|
2462
|
-
/* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
|
|
2463
|
-
] }) : null,
|
|
2464
|
-
/* @__PURE__ */ jsxs6("span", { className: "flex gap-1", children: [
|
|
2465
|
-
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
|
|
2466
|
-
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
|
|
2467
|
-
] })
|
|
2468
|
-
] }) : null
|
|
2508
|
+
refusal2 ? /* @__PURE__ */ jsx10(CellRefusalNotice, { refusal: refusal2, field, row, editing, typed: true }) : null
|
|
2469
2509
|
]
|
|
2470
2510
|
}
|
|
2471
2511
|
)
|
|
2472
2512
|
);
|
|
2473
2513
|
}
|
|
2514
|
+
function CellRefusalNotice({
|
|
2515
|
+
refusal: refusal2,
|
|
2516
|
+
field,
|
|
2517
|
+
row,
|
|
2518
|
+
editing,
|
|
2519
|
+
typed
|
|
2520
|
+
}) {
|
|
2521
|
+
if (refusal2.conflict) {
|
|
2522
|
+
return /* @__PURE__ */ jsx10(
|
|
2523
|
+
RefusalNotice,
|
|
2524
|
+
{
|
|
2525
|
+
error: refusal2.error,
|
|
2526
|
+
className: "py-1.5",
|
|
2527
|
+
actions: /* @__PURE__ */ jsxs6("span", { className: "flex flex-col gap-1", "data-refusal-conflict": "", children: [
|
|
2528
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-[11px]", children: [
|
|
2529
|
+
"Theirs: ",
|
|
2530
|
+
/* @__PURE__ */ jsx10("strong", { children: formatTheirs(refusal2.conflict, field.key) })
|
|
2531
|
+
] }),
|
|
2532
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex flex-wrap gap-1", children: [
|
|
2533
|
+
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "outline", onClick: () => editing?.retry(row.id, field.key), children: "Try again" }),
|
|
2534
|
+
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => editing?.keepTheirs(row.id, field.key), children: "Keep theirs" })
|
|
2535
|
+
] })
|
|
2536
|
+
] })
|
|
2537
|
+
}
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
return /* @__PURE__ */ jsx10(
|
|
2541
|
+
RefusalNotice,
|
|
2542
|
+
{
|
|
2543
|
+
error: refusal2.error,
|
|
2544
|
+
className: "py-1.5",
|
|
2545
|
+
...typed ? { onKeepEditing: () => editing?.keepEditing(row.id, field.key) } : {},
|
|
2546
|
+
onDiscard: () => editing?.discard(row.id, field.key)
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2474
2550
|
function WhoBadge({
|
|
2475
2551
|
field,
|
|
2476
2552
|
row,
|
|
@@ -2523,7 +2599,8 @@ function OpenCell({
|
|
|
2523
2599
|
draft,
|
|
2524
2600
|
onType,
|
|
2525
2601
|
onCommit,
|
|
2526
|
-
onCancel
|
|
2602
|
+
onCancel,
|
|
2603
|
+
below
|
|
2527
2604
|
}) {
|
|
2528
2605
|
const multiline = takesANewline(field);
|
|
2529
2606
|
const blurCommits = closesOnBlur(field);
|
|
@@ -2552,6 +2629,7 @@ function OpenCell({
|
|
|
2552
2629
|
className: "flex w-full min-w-[10rem] flex-col gap-1",
|
|
2553
2630
|
onDoubleClick: (event) => event.stopPropagation(),
|
|
2554
2631
|
onKeyDown: (event) => {
|
|
2632
|
+
if (event.target?.closest?.("[data-refusal-notice]")) return;
|
|
2555
2633
|
if (event.key === "Escape") {
|
|
2556
2634
|
event.preventDefault();
|
|
2557
2635
|
event.stopPropagation();
|
|
@@ -2577,7 +2655,8 @@ function OpenCell({
|
|
|
2577
2655
|
blurCommits ? /* @__PURE__ */ jsx10("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__ */ jsxs6("span", { className: "flex items-center gap-1", children: [
|
|
2578
2656
|
/* @__PURE__ */ jsx10(Button5, { size: "sm", onClick: () => once(() => onCommit(null)), children: "Save" }),
|
|
2579
2657
|
/* @__PURE__ */ jsx10(Button5, { size: "sm", variant: "ghost", onClick: () => once(onCancel), children: "Cancel" })
|
|
2580
|
-
] })
|
|
2658
|
+
] }),
|
|
2659
|
+
below
|
|
2581
2660
|
]
|
|
2582
2661
|
}
|
|
2583
2662
|
);
|
|
@@ -6604,8 +6683,8 @@ function TableSettings({
|
|
|
6604
6683
|
/* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => setAskingToRemove(null), children: "Keep it" })
|
|
6605
6684
|
] }) : null,
|
|
6606
6685
|
/* @__PURE__ */ jsxs17("ul", { className: "divide-y rounded border", children: [
|
|
6607
|
-
rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
6608
|
-
/* @__PURE__ */ jsx21("span", { className: "min-w-
|
|
6686
|
+
rows.map((field, index) => /* @__PURE__ */ jsxs17("li", { "data-settings-field": "", className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
|
|
6687
|
+
/* @__PURE__ */ jsx21("span", { "data-settings-field-name": "", className: "min-w-[8rem] flex-1 break-words", children: fieldName(field) }),
|
|
6609
6688
|
/* @__PURE__ */ jsx21(Badge6, { variant: "outline", className: "text-[10px] font-normal", children: fieldTypeLabel(field) }),
|
|
6610
6689
|
field.required ? /* @__PURE__ */ jsx21("span", { className: "text-[10px] text-destructive", children: "required" }) : null,
|
|
6611
6690
|
/* @__PURE__ */ jsx21(
|
|
@@ -6662,9 +6741,9 @@ function TableSettings({
|
|
|
6662
6741
|
/* @__PURE__ */ jsx21("span", { className: "font-medium", children: "Proposed" }),
|
|
6663
6742
|
/* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: proposals?.length ?? 0 })
|
|
6664
6743
|
] }),
|
|
6665
|
-
proposals === void 0 ? /* @__PURE__ */ jsx21("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__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
6666
|
-
/* @__PURE__ */ jsx21("span", { className: "min-w-
|
|
6667
|
-
/* @__PURE__ */ jsx21("span", { className: "min-w-
|
|
6744
|
+
proposals === void 0 ? /* @__PURE__ */ jsx21("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__ */ jsx21("p", { className: "text-muted-foreground", children: "No field is waiting for a decision." }) : /* @__PURE__ */ jsx21("ul", { className: "divide-y rounded border", children: proposals.map((proposal) => /* @__PURE__ */ jsxs17("li", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-2 py-1.5", children: [
|
|
6745
|
+
/* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words", children: proposal.field.label || proposal.field.key }),
|
|
6746
|
+
/* @__PURE__ */ jsx21("span", { className: "min-w-[8rem] flex-1 break-words text-muted-foreground", children: proposal.why }),
|
|
6668
6747
|
/* @__PURE__ */ jsx21(Badge6, { variant: "secondary", className: "text-[10px] font-normal", children: proposal.proposed_by }),
|
|
6669
6748
|
/* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "outline", onClick: () => onAcceptProposal?.(proposal), children: "Accept" }),
|
|
6670
6749
|
/* @__PURE__ */ jsx21(Button16, { size: "sm", variant: "ghost", onClick: () => onRejectProposal?.(proposal), children: "Reject" })
|
|
@@ -7033,6 +7112,8 @@ function RecordForm({
|
|
|
7033
7112
|
const mutation = useRecordMutation();
|
|
7034
7113
|
const [draft, setDraft] = useState21({});
|
|
7035
7114
|
const [touched, setTouched] = useState21(false);
|
|
7115
|
+
const [changed, setChanged] = useState21({});
|
|
7116
|
+
const [answered, setAnswered] = useState21(null);
|
|
7036
7117
|
const loaded = useRecordVersion(recordId ?? null);
|
|
7037
7118
|
const loadedVersion = loaded.version;
|
|
7038
7119
|
useEffect12(() => {
|
|
@@ -7053,8 +7134,38 @@ function RecordForm({
|
|
|
7053
7134
|
);
|
|
7054
7135
|
if (fields.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: fields.error, className });
|
|
7055
7136
|
if (existing.error) return /* @__PURE__ */ jsx23(RefusalNotice, { error: existing.error, className });
|
|
7137
|
+
const storeSaid = mutation.error && mutation.error !== answered ? mutation.error : null;
|
|
7138
|
+
const fieldProblems = predicted.filter((p) => p.field_key);
|
|
7139
|
+
const problemsShown = (key) => touched || changed[key] ? fieldProblems.filter((p) => p.field_key === key) : [];
|
|
7140
|
+
const storeRefusalIsOnAField = storeSaid !== null && !mutation.conflict && storeSaid.code === "refused_by_rule" && (fields.data ?? []).some((f) => problemsShown(f.key).length > 0);
|
|
7141
|
+
const stored = existing.data?.document;
|
|
7142
|
+
const focusField = (field) => {
|
|
7143
|
+
if (!field) return;
|
|
7144
|
+
globalThis.document?.getElementById(`field-${field.id}`)?.focus();
|
|
7145
|
+
};
|
|
7146
|
+
const keepEditing = (field) => {
|
|
7147
|
+
if (storeSaid) setAnswered(storeSaid);
|
|
7148
|
+
const first = field ?? (fields.data ?? []).find((f) => problemsShown(f.key).length > 0) ?? (fields.data ?? [])[0];
|
|
7149
|
+
focusField(first);
|
|
7150
|
+
};
|
|
7151
|
+
const discard = (field) => {
|
|
7152
|
+
if (storeSaid) setAnswered(storeSaid);
|
|
7153
|
+
if (field) {
|
|
7154
|
+
setDraft((d) => ({ ...d, [field.key]: stored ? stored[field.key] : void 0 }));
|
|
7155
|
+
setChanged((c) => {
|
|
7156
|
+
const next = { ...c };
|
|
7157
|
+
delete next[field.key];
|
|
7158
|
+
return next;
|
|
7159
|
+
});
|
|
7160
|
+
return;
|
|
7161
|
+
}
|
|
7162
|
+
setDraft(stored ? { ...stored } : {});
|
|
7163
|
+
setChanged({});
|
|
7164
|
+
setTouched(false);
|
|
7165
|
+
};
|
|
7056
7166
|
const save = async () => {
|
|
7057
7167
|
setTouched(true);
|
|
7168
|
+
setAnswered(null);
|
|
7058
7169
|
if (recordId) {
|
|
7059
7170
|
const version = loadedVersion;
|
|
7060
7171
|
const moved = await mutation.update({
|
|
@@ -7091,8 +7202,13 @@ function RecordForm({
|
|
|
7091
7202
|
field,
|
|
7092
7203
|
value: draft[field.key],
|
|
7093
7204
|
document: existing.data?.document,
|
|
7094
|
-
problems:
|
|
7095
|
-
onChange: (value) =>
|
|
7205
|
+
problems: problemsShown(field.key),
|
|
7206
|
+
onChange: (value) => {
|
|
7207
|
+
setDraft((d) => ({ ...d, [field.key]: value }));
|
|
7208
|
+
setChanged((c) => c[field.key] ? c : { ...c, [field.key]: true });
|
|
7209
|
+
},
|
|
7210
|
+
onKeepEditing: () => keepEditing(field),
|
|
7211
|
+
onDiscard: () => discard(field)
|
|
7096
7212
|
},
|
|
7097
7213
|
field.id
|
|
7098
7214
|
)) }),
|
|
@@ -7103,10 +7219,10 @@ function RecordForm({
|
|
|
7103
7219
|
},
|
|
7104
7220
|
p.message
|
|
7105
7221
|
)) : null,
|
|
7106
|
-
|
|
7222
|
+
storeSaid && !storeRefusalIsOnAField ? /* @__PURE__ */ jsx23(
|
|
7107
7223
|
RefusalNotice,
|
|
7108
7224
|
{
|
|
7109
|
-
error:
|
|
7225
|
+
error: storeSaid,
|
|
7110
7226
|
...mutation.conflict ? {
|
|
7111
7227
|
actions: /* @__PURE__ */ jsxs19("div", { className: "mt-1 space-y-1", children: [
|
|
7112
7228
|
Object.entries(mutation.conflict.contested_fields).map(([key, theirs]) => {
|
|
@@ -7127,14 +7243,16 @@ function RecordForm({
|
|
|
7127
7243
|
size: "sm",
|
|
7128
7244
|
variant: "outline",
|
|
7129
7245
|
onClick: () => {
|
|
7246
|
+
if (mutation.conflict) loaded.note(mutation.conflict.current_version);
|
|
7130
7247
|
existing.reload();
|
|
7131
7248
|
mutation.clearConflict();
|
|
7249
|
+
setAnswered(storeSaid);
|
|
7132
7250
|
},
|
|
7133
7251
|
children: "Take theirs and start again"
|
|
7134
7252
|
}
|
|
7135
7253
|
)
|
|
7136
7254
|
] })
|
|
7137
|
-
} : {}
|
|
7255
|
+
} : { onKeepEditing: () => keepEditing(), onDiscard: () => discard() }
|
|
7138
7256
|
}
|
|
7139
7257
|
) : null,
|
|
7140
7258
|
/* @__PURE__ */ jsx23(Separator7, {}),
|
|
@@ -7151,17 +7269,20 @@ function FieldRow({
|
|
|
7151
7269
|
value,
|
|
7152
7270
|
document: document2,
|
|
7153
7271
|
problems,
|
|
7154
|
-
onChange
|
|
7272
|
+
onChange,
|
|
7273
|
+
onKeepEditing,
|
|
7274
|
+
onDiscard
|
|
7155
7275
|
}) {
|
|
7156
7276
|
const id = `field-${field.id}`;
|
|
7157
7277
|
return /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
7158
7278
|
/* @__PURE__ */ jsx23(FieldLabel, { field, htmlFor: id, children: /* @__PURE__ */ jsx23(ProvenanceBadge, { document: document2, fieldKey: field.key }) }),
|
|
7159
7279
|
/* @__PURE__ */ jsx23(FieldControl, { field, value, onChange, id }),
|
|
7160
|
-
problems.map((problem) => /* @__PURE__ */ jsx23(
|
|
7280
|
+
problems.map((problem, index) => /* @__PURE__ */ jsx23(
|
|
7161
7281
|
RefusalNotice,
|
|
7162
7282
|
{
|
|
7163
7283
|
error: { code: "refused_by_rule", message: problem.message },
|
|
7164
|
-
className: "border-0 p-0"
|
|
7284
|
+
className: "border-0 p-0",
|
|
7285
|
+
...index === 0 ? { onKeepEditing, onDiscard } : {}
|
|
7165
7286
|
},
|
|
7166
7287
|
problem.message
|
|
7167
7288
|
))
|
|
@@ -7331,6 +7452,7 @@ function computedInPlainWords(row, field) {
|
|
|
7331
7452
|
}
|
|
7332
7453
|
|
|
7333
7454
|
// src/views.ts
|
|
7455
|
+
import { asWriteConflict as asWriteConflict2 } from "@ai-matrx/records/core";
|
|
7334
7456
|
var VIEW_LAYOUTS = ["grid", "kanban", "calendar", "gallery"];
|
|
7335
7457
|
var LAYOUT_LABEL = {
|
|
7336
7458
|
grid: "Grid",
|
|
@@ -7454,6 +7576,18 @@ function viewFromRecord(row) {
|
|
|
7454
7576
|
presentation: parseGridPresentation(data["presentation"])
|
|
7455
7577
|
};
|
|
7456
7578
|
}
|
|
7579
|
+
async function saveViewPatch(client, viewId, document2, expectedVersion) {
|
|
7580
|
+
const send = (expected) => client.recordUpdate({
|
|
7581
|
+
record_id: viewId,
|
|
7582
|
+
patch: document2,
|
|
7583
|
+
...expected === null ? {} : { expectedVersion: expected }
|
|
7584
|
+
});
|
|
7585
|
+
const first = await send(expectedVersion);
|
|
7586
|
+
if (first.ok) return first;
|
|
7587
|
+
const conflict = asWriteConflict2(first.error, viewId);
|
|
7588
|
+
if (!conflict) return first;
|
|
7589
|
+
return send(conflict.current_version);
|
|
7590
|
+
}
|
|
7457
7591
|
function viewPatchDocument(patch) {
|
|
7458
7592
|
const out = {};
|
|
7459
7593
|
if (patch.name !== void 0) out["name"] = patch.name;
|
|
@@ -11881,6 +12015,21 @@ function whenItFires(subscription) {
|
|
|
11881
12015
|
const every = subscription.cadence === "hourly" ? "an hourly summary" : subscription.cadence === "weekly" ? "a weekly summary" : "a daily summary";
|
|
11882
12016
|
return subscription.schedule ? `${every}, ${subscription.schedule}` : every;
|
|
11883
12017
|
}
|
|
12018
|
+
var PERIOD_MS = {
|
|
12019
|
+
hourly: 60 * 60 * 1e3,
|
|
12020
|
+
daily: 24 * 60 * 60 * 1e3,
|
|
12021
|
+
weekly: 7 * 24 * 60 * 60 * 1e3
|
|
12022
|
+
};
|
|
12023
|
+
function nextSummaryAt(subscription, now) {
|
|
12024
|
+
if (!subscription.next_digest_at) return null;
|
|
12025
|
+
const due = new Date(subscription.next_digest_at);
|
|
12026
|
+
if (Number.isNaN(due.getTime())) return null;
|
|
12027
|
+
if (due.getTime() > now.getTime()) return due;
|
|
12028
|
+
const period = PERIOD_MS[subscription.cadence];
|
|
12029
|
+
if (!period) return due;
|
|
12030
|
+
const behind = now.getTime() - due.getTime();
|
|
12031
|
+
return new Date(due.getTime() + (Math.floor(behind / period) + 1) * period);
|
|
12032
|
+
}
|
|
11884
12033
|
var CHANNEL_WORDS2 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
11885
12034
|
var DIGEST_SUGGESTION = "Email me a summary of this every Monday at 8 in the morning.";
|
|
11886
12035
|
function SubscriptionsPanel({ tableId, activeRuleId, className }) {
|
|
@@ -12008,7 +12157,7 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
|
|
|
12008
12157
|
/* @__PURE__ */ jsxs37("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
12009
12158
|
subscription.next_digest_at ? /* @__PURE__ */ jsxs37("span", { children: [
|
|
12010
12159
|
"Next summary ",
|
|
12011
|
-
new Date(
|
|
12160
|
+
nextSummaryAt(subscription, /* @__PURE__ */ new Date())?.toLocaleString()
|
|
12012
12161
|
] }) : subscription.cadence === "instant" ? null : subscription.muted ? /* @__PURE__ */ jsx41("span", { children: "No next summary while it is off." }) : null,
|
|
12013
12162
|
subscription.last_sent_at ? /* @__PURE__ */ jsxs37("span", { children: [
|
|
12014
12163
|
"Last told you ",
|
|
@@ -16202,13 +16351,31 @@ var LANE_EMPTY = {
|
|
|
16202
16351
|
community: "Nothing shared beyond your organization. A table published by link or to the world lands here.",
|
|
16203
16352
|
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."
|
|
16204
16353
|
};
|
|
16354
|
+
function keptByTheApp(table) {
|
|
16355
|
+
return table.kept_by_the_app === true;
|
|
16356
|
+
}
|
|
16205
16357
|
function laneFor(table) {
|
|
16206
16358
|
if (table.is_kernel) return "system";
|
|
16207
|
-
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX)) return "app";
|
|
16359
|
+
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return "app";
|
|
16208
16360
|
if (table.visibility === "public" || table.visibility === "link") return "community";
|
|
16209
16361
|
if (table.visibility === "personal") return "mine";
|
|
16210
16362
|
return "organization";
|
|
16211
16363
|
}
|
|
16364
|
+
var VISIBILITY_LANES = ["mine", "organization", "community", "world"];
|
|
16365
|
+
var VISIBILITY_LANE_TITLE = {
|
|
16366
|
+
mine: "Mine",
|
|
16367
|
+
organization: "My organization",
|
|
16368
|
+
community: "Community",
|
|
16369
|
+
world: "World"
|
|
16370
|
+
};
|
|
16371
|
+
function visibilityLaneFor(table) {
|
|
16372
|
+
if (table.is_kernel) return null;
|
|
16373
|
+
if ((table.slug ?? "").startsWith(APP_TABLE_PREFIX) || keptByTheApp(table)) return null;
|
|
16374
|
+
if (table.visibility === "personal") return "mine";
|
|
16375
|
+
if (table.visibility === "link") return "community";
|
|
16376
|
+
if (table.visibility === "public") return "world";
|
|
16377
|
+
return "organization";
|
|
16378
|
+
}
|
|
16212
16379
|
function TablesHome({ onOpenTable, className }) {
|
|
16213
16380
|
const client = useRecordsClient42();
|
|
16214
16381
|
const tables = useTables4();
|
|
@@ -16354,8 +16521,45 @@ function TablesHome({ onOpenTable, className }) {
|
|
|
16354
16521
|
// src/TablePage.tsx
|
|
16355
16522
|
import { useCallback as useCallback40, useEffect as useEffect46, useState as useState57 } from "react";
|
|
16356
16523
|
import { useFields as useFields24, useRecordsClient as useRecordsClient43, useTable as useTable23 } from "@ai-matrx/records/react";
|
|
16357
|
-
import { Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54 } from "@ai-matrx/design-system";
|
|
16524
|
+
import { BottomSheet, Button as Button51, Separator as Separator12, Skeleton as Skeleton36, cn as cn54, useIsMobile } from "@ai-matrx/design-system";
|
|
16358
16525
|
import { Fragment as Fragment28, jsx as jsx61, jsxs as jsxs56 } from "react/jsx-runtime";
|
|
16526
|
+
var RAIL_TITLE = {
|
|
16527
|
+
settings: "Table settings",
|
|
16528
|
+
inbox: "Inbox",
|
|
16529
|
+
import: "Import",
|
|
16530
|
+
field: "Add a field",
|
|
16531
|
+
record: "Record",
|
|
16532
|
+
"new-record": "New record",
|
|
16533
|
+
forms: "Forms",
|
|
16534
|
+
bookings: "Bookings",
|
|
16535
|
+
checklists: "Checklists",
|
|
16536
|
+
notifications: "Notifications",
|
|
16537
|
+
portals: "Portals",
|
|
16538
|
+
"who-changed": "Who changed this"
|
|
16539
|
+
};
|
|
16540
|
+
function RailFrame({
|
|
16541
|
+
phone,
|
|
16542
|
+
title,
|
|
16543
|
+
onClose,
|
|
16544
|
+
children
|
|
16545
|
+
}) {
|
|
16546
|
+
if (phone) {
|
|
16547
|
+
return /* @__PURE__ */ jsx61(
|
|
16548
|
+
BottomSheet,
|
|
16549
|
+
{
|
|
16550
|
+
open: true,
|
|
16551
|
+
onOpenChange: (open) => {
|
|
16552
|
+
if (!open) onClose();
|
|
16553
|
+
},
|
|
16554
|
+
title,
|
|
16555
|
+
size: "full",
|
|
16556
|
+
surface: "solid",
|
|
16557
|
+
children: /* @__PURE__ */ jsx61("div", { "data-rail-sheet": "", className: "min-h-0 flex-1 overflow-y-auto px-3 pb-6", children })
|
|
16558
|
+
}
|
|
16559
|
+
);
|
|
16560
|
+
}
|
|
16561
|
+
return /* @__PURE__ */ jsx61("aside", { "data-rail-column": "", className: "w-[26rem] shrink-0 overflow-y-auto rounded-md border p-3", children });
|
|
16562
|
+
}
|
|
16359
16563
|
function filterInWords(filter, fields) {
|
|
16360
16564
|
const nameOf = (key) => {
|
|
16361
16565
|
const field = fields.find((f) => f.key === key);
|
|
@@ -16495,6 +16699,7 @@ function TablePage({
|
|
|
16495
16699
|
const setRail = (next) => setSurface((now) => ({ ...now, rail: next }));
|
|
16496
16700
|
const [openRecord, setOpenRecord] = useState57(opening.record);
|
|
16497
16701
|
const viewVersion = useRecordVersion(view?.id ?? null);
|
|
16702
|
+
const phone = useIsMobile();
|
|
16498
16703
|
const shownLayout = layoutFromLink ?? view?.layout ?? "grid";
|
|
16499
16704
|
const press = (pressed) => {
|
|
16500
16705
|
const next = chooseSurface(surface, pressed);
|
|
@@ -16522,6 +16727,7 @@ function TablePage({
|
|
|
16522
16727
|
setLayoutFromLink(isLayout ? named : null);
|
|
16523
16728
|
setSurface((now) => ({ ...now, main: isLayout ? "records" : named }));
|
|
16524
16729
|
}, [activeView]);
|
|
16730
|
+
const [viewSaveRefused, setViewSaveRefused] = useState57(null);
|
|
16525
16731
|
const patchView = useCallback40(
|
|
16526
16732
|
async (patch) => {
|
|
16527
16733
|
const current = view;
|
|
@@ -16529,12 +16735,13 @@ function TablePage({
|
|
|
16529
16735
|
setView({ ...current, ...patch });
|
|
16530
16736
|
const document2 = viewPatchDocument(patch);
|
|
16531
16737
|
if (Object.keys(document2).length === 0) return;
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
|
|
16536
|
-
|
|
16537
|
-
|
|
16738
|
+
setViewSaveRefused(null);
|
|
16739
|
+
const written = await saveViewPatch(client, current.id, document2, viewVersion.version);
|
|
16740
|
+
if (written.ok) {
|
|
16741
|
+
viewVersion.note(written.data);
|
|
16742
|
+
return;
|
|
16743
|
+
}
|
|
16744
|
+
setViewSaveRefused({ error: written.error, patch, before: current });
|
|
16538
16745
|
},
|
|
16539
16746
|
[client, view, viewVersion]
|
|
16540
16747
|
);
|
|
@@ -16685,6 +16892,27 @@ function TablePage({
|
|
|
16685
16892
|
),
|
|
16686
16893
|
/* @__PURE__ */ jsx61(ExportMenu, { tableId })
|
|
16687
16894
|
] }),
|
|
16895
|
+
viewSaveRefused ? /* @__PURE__ */ jsx61(
|
|
16896
|
+
RefusalNotice,
|
|
16897
|
+
{
|
|
16898
|
+
error: viewSaveRefused.error,
|
|
16899
|
+
actions: /* @__PURE__ */ jsxs56("span", { className: "flex flex-wrap gap-1 pt-0.5", "data-view-save-refused": "", children: [
|
|
16900
|
+
/* @__PURE__ */ jsx61(Button51, { size: "sm", variant: "outline", onClick: () => void patchView(viewSaveRefused.patch), children: "Try again" }),
|
|
16901
|
+
/* @__PURE__ */ jsx61(
|
|
16902
|
+
Button51,
|
|
16903
|
+
{
|
|
16904
|
+
size: "sm",
|
|
16905
|
+
variant: "ghost",
|
|
16906
|
+
onClick: () => {
|
|
16907
|
+
setView(viewSaveRefused.before);
|
|
16908
|
+
setViewSaveRefused(null);
|
|
16909
|
+
},
|
|
16910
|
+
children: "Discard"
|
|
16911
|
+
}
|
|
16912
|
+
)
|
|
16913
|
+
] })
|
|
16914
|
+
}
|
|
16915
|
+
) : null,
|
|
16688
16916
|
main === "dashboards" ? /* @__PURE__ */ jsx61(DashboardCanvas, { tableId, activeDashboardId: activeDashboardId ?? null }) : main === "archived" ? (
|
|
16689
16917
|
/* THE ROWS THAT LEFT. Restoring one puts it back in the grid, so the
|
|
16690
16918
|
page returns to the records rather than leaving the person looking
|
|
@@ -16745,7 +16973,7 @@ function TablePage({
|
|
|
16745
16973
|
view ? null : /* @__PURE__ */ jsx61("p", { className: "text-xs text-muted-foreground", children: VIEW_NOT_SAVED_YET })
|
|
16746
16974
|
] })
|
|
16747
16975
|
] }),
|
|
16748
|
-
rail === "none" ? null : /* @__PURE__ */ jsxs56(
|
|
16976
|
+
rail === "none" ? null : /* @__PURE__ */ jsxs56(RailFrame, { phone, title: RAIL_TITLE[rail], onClose: () => setRail("none"), children: [
|
|
16749
16977
|
rail === "settings" ? /* @__PURE__ */ jsx61(
|
|
16750
16978
|
TableSettings,
|
|
16751
16979
|
{
|
|
@@ -16984,6 +17212,8 @@ export {
|
|
|
16984
17212
|
VIEW_LAYOUTS,
|
|
16985
17213
|
VIEW_NOT_SAVED_YET,
|
|
16986
17214
|
VIEW_TABLE,
|
|
17215
|
+
VISIBILITY_LANES,
|
|
17216
|
+
VISIBILITY_LANE_TITLE,
|
|
16987
17217
|
ViewBar,
|
|
16988
17218
|
ViewSwitcher,
|
|
16989
17219
|
WITHHELD_RECORD_LABEL,
|
|
@@ -17039,6 +17269,7 @@ export {
|
|
|
17039
17269
|
isPlainFieldType,
|
|
17040
17270
|
isRelationFieldType,
|
|
17041
17271
|
isSignatureField,
|
|
17272
|
+
keptByTheApp,
|
|
17042
17273
|
keyFor,
|
|
17043
17274
|
kindsWithNoChoice,
|
|
17044
17275
|
laneFor,
|
|
@@ -17074,6 +17305,7 @@ export {
|
|
|
17074
17305
|
revokeConsequence,
|
|
17075
17306
|
rowName,
|
|
17076
17307
|
rowNameIn,
|
|
17308
|
+
saveViewPatch,
|
|
17077
17309
|
scalarText,
|
|
17078
17310
|
shareUnavailableReason,
|
|
17079
17311
|
specFromBlock,
|
|
@@ -17103,6 +17335,7 @@ export {
|
|
|
17103
17335
|
viewDocument,
|
|
17104
17336
|
viewFromRecord,
|
|
17105
17337
|
viewPatchDocument,
|
|
17338
|
+
visibilityLaneFor,
|
|
17106
17339
|
whatIsMissing,
|
|
17107
17340
|
whatYouMayDo,
|
|
17108
17341
|
whatYouMayDoWithTable,
|