@ai-matrx/records-ui 0.56.0 → 0.58.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 +68 -0
- package/dist/index.cjs +384 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +392 -232
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2731,9 +2731,18 @@ function EnrichPanel({ tableId, fieldId, className }) {
|
|
|
2731
2731
|
var import_react17 = require("react");
|
|
2732
2732
|
var import_design_system8 = require("@ai-matrx/design-system");
|
|
2733
2733
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2734
|
-
function PastePreview({
|
|
2734
|
+
function PastePreview({
|
|
2735
|
+
plan,
|
|
2736
|
+
noun,
|
|
2737
|
+
nounPlural,
|
|
2738
|
+
onConfirm,
|
|
2739
|
+
onCancel,
|
|
2740
|
+
onAddOptions
|
|
2741
|
+
}) {
|
|
2735
2742
|
const [busy, setBusy] = (0, import_react17.useState)(false);
|
|
2736
2743
|
const [outcome, setOutcome] = (0, import_react17.useState)(null);
|
|
2744
|
+
const [adding, setAdding] = (0, import_react17.useState)(null);
|
|
2745
|
+
const [addFailed, setAddFailed] = (0, import_react17.useState)(null);
|
|
2737
2746
|
const nothingToDo = plan.cells === 0;
|
|
2738
2747
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2739
2748
|
"div",
|
|
@@ -2772,6 +2781,37 @@ function PastePreview({ plan, noun, nounPlural, onConfirm, onCancel }) {
|
|
|
2772
2781
|
" more of the same kinds."
|
|
2773
2782
|
] }) : null
|
|
2774
2783
|
] }) : null,
|
|
2784
|
+
onAddOptions && plan.unmatchedOptions.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-matrx-paste-add-options": true, className: "flex flex-col gap-2", children: [
|
|
2785
|
+
plan.unmatchedOptions.map((group) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2786
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-xs", children: [
|
|
2787
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "font-medium", children: group.column }),
|
|
2788
|
+
" does not offer",
|
|
2789
|
+
" ",
|
|
2790
|
+
group.words.map((w) => `\u201C${w}\u201D`).join(", "),
|
|
2791
|
+
"."
|
|
2792
|
+
] }),
|
|
2793
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2794
|
+
import_design_system8.Button,
|
|
2795
|
+
{
|
|
2796
|
+
type: "button",
|
|
2797
|
+
size: "sm",
|
|
2798
|
+
variant: "outline",
|
|
2799
|
+
disabled: adding !== null || busy,
|
|
2800
|
+
onClick: () => {
|
|
2801
|
+
setAddFailed(null);
|
|
2802
|
+
setAdding(group.fieldKey);
|
|
2803
|
+
void onAddOptions(group.fieldKey, group.words).catch(
|
|
2804
|
+
(err) => setAddFailed(
|
|
2805
|
+
`Those options were not added: ${err instanceof Error ? err.message : String(err)}`
|
|
2806
|
+
)
|
|
2807
|
+
).finally(() => setAdding(null));
|
|
2808
|
+
},
|
|
2809
|
+
children: adding === group.fieldKey ? "Adding\u2026" : group.words.length === 1 ? `Add \u201C${group.words[0]}\u201D as an option` : `Add these ${group.words.length} options`
|
|
2810
|
+
}
|
|
2811
|
+
)
|
|
2812
|
+
] }, group.fieldKey)),
|
|
2813
|
+
addFailed ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-destructive", children: addFailed }) : null
|
|
2814
|
+
] }) : null,
|
|
2775
2815
|
plan.pastRightEdge > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
|
|
2776
2816
|
plan.pastRightEdge,
|
|
2777
2817
|
" ",
|
|
@@ -2863,9 +2903,26 @@ function confirmLabel(plan, noun, nounPlural) {
|
|
|
2863
2903
|
|
|
2864
2904
|
// src/pasteValues.ts
|
|
2865
2905
|
var import_core2 = require("@ai-matrx/records/core");
|
|
2906
|
+
var import_records5 = require("@ai-matrx/records");
|
|
2907
|
+
function choiceOptionsOf(options) {
|
|
2908
|
+
const byWord = /* @__PURE__ */ new Map();
|
|
2909
|
+
const labels = [];
|
|
2910
|
+
for (const option of options) {
|
|
2911
|
+
const id = option.id;
|
|
2912
|
+
const key = (0, import_records5.optionKey)(option);
|
|
2913
|
+
const label = recordName(option.data, null, "");
|
|
2914
|
+
if (label) {
|
|
2915
|
+
labels.push(label);
|
|
2916
|
+
if (!byWord.has(label.toLowerCase())) byWord.set(label.toLowerCase(), id);
|
|
2917
|
+
}
|
|
2918
|
+
if (key && !byWord.has(String(key).toLowerCase())) byWord.set(String(key).toLowerCase(), id);
|
|
2919
|
+
if (id && !byWord.has(id.toLowerCase())) byWord.set(id.toLowerCase(), id);
|
|
2920
|
+
}
|
|
2921
|
+
return { byWord, labels };
|
|
2922
|
+
}
|
|
2866
2923
|
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "y", "1", "\u2713", "x", "checked", "on"]);
|
|
2867
2924
|
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "n", "0", "", "unchecked", "off"]);
|
|
2868
|
-
function valueFromPastedText(field, raw) {
|
|
2925
|
+
function valueFromPastedText(field, raw, options) {
|
|
2869
2926
|
const label = fieldName(field);
|
|
2870
2927
|
const text = raw.trim();
|
|
2871
2928
|
const kind = editorKindFor(field);
|
|
@@ -2925,10 +2982,30 @@ function valueFromPastedText(field, raw) {
|
|
|
2925
2982
|
// stop agreeing with it. So the cell is left alone and the person is told
|
|
2926
2983
|
// which control does hold it.
|
|
2927
2984
|
case "select":
|
|
2928
|
-
case "multi_select":
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2985
|
+
case "multi_select": {
|
|
2986
|
+
if (!options) {
|
|
2987
|
+
return {
|
|
2988
|
+
refusal: `${label} is a list and its options have not been read yet, so nothing can be matched to \u201C${raw}\u201D. Reopen the paste once the column's choices have loaded.`
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
const wanted = kind === "multi_select" ? text.split(/[,;]/).map((w) => w.trim()).filter((w) => w.length > 0) : [text];
|
|
2992
|
+
if (wanted.length === 0) return { value: kind === "multi_select" ? [] : null };
|
|
2993
|
+
const matched = [];
|
|
2994
|
+
const unmatched = [];
|
|
2995
|
+
for (const word of wanted) {
|
|
2996
|
+
const hit = options.byWord.get(word.toLowerCase());
|
|
2997
|
+
if (hit === void 0) unmatched.push(word);
|
|
2998
|
+
else matched.push(hit);
|
|
2999
|
+
}
|
|
3000
|
+
if (unmatched.length > 0) {
|
|
3001
|
+
const offered = options.labels.length > 0 ? options.labels.join(", ") : "it has none yet";
|
|
3002
|
+
return {
|
|
3003
|
+
refusal: `${label} does not have ${unmatched.map((w) => `\u201C${w}\u201D`).join(" or ")} among its options. It offers: ${offered}.`,
|
|
3004
|
+
unmatched
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
return { value: kind === "multi_select" ? matched : matched[0] };
|
|
3008
|
+
}
|
|
2932
3009
|
case "member":
|
|
2933
3010
|
return {
|
|
2934
3011
|
refusal: `${label} names a person in this organization, so it is chosen rather than typed. Open the cell and pick them.`
|
|
@@ -2955,6 +3032,7 @@ function planPastedBlock(args) {
|
|
|
2955
3032
|
const byKey = new Map(args.fields.map((f) => [f.key, f]));
|
|
2956
3033
|
const rows = [];
|
|
2957
3034
|
const refusals = [];
|
|
3035
|
+
const unmatched = /* @__PURE__ */ new Map();
|
|
2958
3036
|
let cells = 0;
|
|
2959
3037
|
const lines = [
|
|
2960
3038
|
...args.onExisting.map((r) => ({ recordId: r.recordId, raw: r.raw })),
|
|
@@ -2970,8 +3048,15 @@ function planPastedBlock(args) {
|
|
|
2970
3048
|
const field = byKey.get(key);
|
|
2971
3049
|
const raw = line.raw[c];
|
|
2972
3050
|
if (field === void 0 || raw === void 0) continue;
|
|
2973
|
-
const judged = valueFromPastedText(field, raw);
|
|
3051
|
+
const judged = valueFromPastedText(field, raw, args.options?.get(key));
|
|
2974
3052
|
if ("refusal" in judged) {
|
|
3053
|
+
if ("unmatched" in judged) {
|
|
3054
|
+
const seen = unmatched.get(key) ?? { column: fieldName(field), words: [] };
|
|
3055
|
+
for (const word of judged.unmatched) {
|
|
3056
|
+
if (!seen.words.some((w) => w.toLowerCase() === word.toLowerCase())) seen.words.push(word);
|
|
3057
|
+
}
|
|
3058
|
+
unmatched.set(key, seen);
|
|
3059
|
+
}
|
|
2975
3060
|
built.push({ key, raw, value: null, refusal: judged.refusal });
|
|
2976
3061
|
refusals.push({ fromLine, column: fieldName(field), raw, why: judged.refusal });
|
|
2977
3062
|
continue;
|
|
@@ -3007,7 +3092,12 @@ function planPastedBlock(args) {
|
|
|
3007
3092
|
creates: keepers.filter((r) => r.recordId === null).length,
|
|
3008
3093
|
cells,
|
|
3009
3094
|
refusals,
|
|
3010
|
-
pastRightEdge: args.pastRightEdge ?? 0
|
|
3095
|
+
pastRightEdge: args.pastRightEdge ?? 0,
|
|
3096
|
+
unmatchedOptions: [...unmatched.entries()].map(([fieldKey, seen]) => ({
|
|
3097
|
+
fieldKey,
|
|
3098
|
+
column: seen.column,
|
|
3099
|
+
words: seen.words
|
|
3100
|
+
}))
|
|
3011
3101
|
};
|
|
3012
3102
|
}
|
|
3013
3103
|
function documentOf(row) {
|
|
@@ -3089,13 +3179,29 @@ function Grid({
|
|
|
3089
3179
|
[client, host, refreshEnrich]
|
|
3090
3180
|
);
|
|
3091
3181
|
const [pastePlan, setPastePlan] = (0, import_react18.useState)(null);
|
|
3182
|
+
const choiceOptions = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
|
|
3183
|
+
const loadChoiceOptions = (0, import_react18.useCallback)(
|
|
3184
|
+
async (fieldList) => {
|
|
3185
|
+
const wanted = fieldList.filter(
|
|
3186
|
+
(f) => (editorKindFor(f) === "select" || editorKindFor(f) === "multi_select") && !choiceOptions.current.has(f.key)
|
|
3187
|
+
);
|
|
3188
|
+
await Promise.all(
|
|
3189
|
+
wanted.map(async (field) => {
|
|
3190
|
+
const got = await client.fieldOptions({ field_id: field.id });
|
|
3191
|
+
if (got.ok) choiceOptions.current.set(field.key, choiceOptionsOf(got.data ?? []));
|
|
3192
|
+
})
|
|
3193
|
+
);
|
|
3194
|
+
},
|
|
3195
|
+
[client]
|
|
3196
|
+
);
|
|
3092
3197
|
const pasteableKeys = (0, import_react18.useMemo)(
|
|
3093
3198
|
() => ordered.filter((f) => fieldIsEditable(f)).map((f) => f.key),
|
|
3094
3199
|
[ordered]
|
|
3095
3200
|
);
|
|
3096
3201
|
const receivePastePlan = (0, import_react18.useCallback)(
|
|
3097
|
-
(sheetPlan) => {
|
|
3202
|
+
async (sheetPlan) => {
|
|
3098
3203
|
const fieldList = fields.data ?? [];
|
|
3204
|
+
await loadChoiceOptions(fieldList);
|
|
3099
3205
|
const byRecord = /* @__PURE__ */ new Map();
|
|
3100
3206
|
const order = [];
|
|
3101
3207
|
const take = (rowId, columnId, value) => {
|
|
@@ -3123,6 +3229,7 @@ function Grid({
|
|
|
3123
3229
|
);
|
|
3124
3230
|
const built = planPastedBlock({
|
|
3125
3231
|
fields: fieldList,
|
|
3232
|
+
options: choiceOptions.current,
|
|
3126
3233
|
columnKeys,
|
|
3127
3234
|
onExisting: touched.map((recordId) => ({
|
|
3128
3235
|
recordId,
|
|
@@ -3146,7 +3253,37 @@ function Grid({
|
|
|
3146
3253
|
}
|
|
3147
3254
|
setPastePlan({ plan: built });
|
|
3148
3255
|
},
|
|
3149
|
-
[fields.data, rows]
|
|
3256
|
+
[fields.data, loadChoiceOptions, rows]
|
|
3257
|
+
);
|
|
3258
|
+
const addPastedOptions = (0, import_react18.useCallback)(
|
|
3259
|
+
async (fieldKey, words) => {
|
|
3260
|
+
const field = (fields.data ?? []).find((f) => f.key === fieldKey);
|
|
3261
|
+
if (!field) throw new Error(`This table has no column called ${fieldKey}.`);
|
|
3262
|
+
const existing = choiceOptions.current.get(fieldKey)?.labels ?? [];
|
|
3263
|
+
const written = await client.fieldUpdate({
|
|
3264
|
+
field_id: field.id,
|
|
3265
|
+
patch: { options: [...existing, ...words] }
|
|
3266
|
+
});
|
|
3267
|
+
if (!written.ok) throw new Error(written.error.message);
|
|
3268
|
+
choiceOptions.current.delete(fieldKey);
|
|
3269
|
+
await loadChoiceOptions(fields.data ?? []);
|
|
3270
|
+
setPastePlan(
|
|
3271
|
+
(held) => held ? {
|
|
3272
|
+
plan: planPastedBlock({
|
|
3273
|
+
fields: fields.data ?? [],
|
|
3274
|
+
options: choiceOptions.current,
|
|
3275
|
+
columnKeys: held.plan.rows[0]?.cells.map((c) => c.key) ?? [],
|
|
3276
|
+
onExisting: held.plan.rows.filter((r) => r.recordId !== null).map((r) => ({
|
|
3277
|
+
recordId: r.recordId,
|
|
3278
|
+
raw: r.cells.map((c) => c.raw)
|
|
3279
|
+
})),
|
|
3280
|
+
onNew: held.plan.rows.filter((r) => r.recordId === null).map((r) => r.cells.map((c) => c.raw)),
|
|
3281
|
+
pastRightEdge: held.plan.pastRightEdge
|
|
3282
|
+
})
|
|
3283
|
+
} : held
|
|
3284
|
+
);
|
|
3285
|
+
},
|
|
3286
|
+
[client, fields.data, loadChoiceOptions]
|
|
3150
3287
|
);
|
|
3151
3288
|
const applyPaste = (0, import_react18.useCallback)(async () => {
|
|
3152
3289
|
const held = pastePlan;
|
|
@@ -3154,39 +3291,56 @@ function Grid({
|
|
|
3154
3291
|
const refusals = [];
|
|
3155
3292
|
let written = 0;
|
|
3156
3293
|
const expected = [];
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3294
|
+
const creates = held.plan.rows.filter((row) => row.recordId === null).map((row) => ({ row, document: documentOf(row) })).filter((c) => Object.keys(c.document).length > 0);
|
|
3295
|
+
const updates = held.plan.rows.filter((row) => row.recordId !== null).map((row) => ({ row, document: documentOf(row) })).filter((c) => Object.keys(c.document).length > 0);
|
|
3296
|
+
for (let at = 0; at < creates.length; at += pageSize) {
|
|
3297
|
+
const chunk = creates.slice(at, at + pageSize);
|
|
3298
|
+
const made = await client.recordWriteMany({
|
|
3299
|
+
table_id: tableId,
|
|
3300
|
+
rows: chunk.map((c) => c.document)
|
|
3301
|
+
});
|
|
3302
|
+
if (!made.ok) {
|
|
3303
|
+
refusals.push(
|
|
3304
|
+
`Rows ${chunk[0].row.fromLine}-${chunk[chunk.length - 1].row.fromLine}: ${made.error.message}`
|
|
3305
|
+
);
|
|
3306
|
+
continue;
|
|
3307
|
+
}
|
|
3308
|
+
chunk.forEach((c, index) => {
|
|
3309
|
+
const id = made.data[index];
|
|
3310
|
+
if (id === void 0) {
|
|
3311
|
+
refusals.push(`Row ${c.row.fromLine}: the store wrote the batch and named no id for this line.`);
|
|
3312
|
+
return;
|
|
3173
3313
|
}
|
|
3174
3314
|
written += 1;
|
|
3175
|
-
expected.push({ recordId:
|
|
3315
|
+
expected.push({ recordId: id, document: c.document });
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
for (const { row, document: document2 } of updates) {
|
|
3319
|
+
const done = await client.recordUpdate({ record_id: row.recordId, patch: document2 });
|
|
3320
|
+
if (!done.ok) {
|
|
3321
|
+
refusals.push(`Row ${row.fromLine}: ${done.error.message}`);
|
|
3322
|
+
continue;
|
|
3176
3323
|
}
|
|
3324
|
+
written += 1;
|
|
3325
|
+
expected.push({ recordId: row.recordId, document: document2 });
|
|
3177
3326
|
}
|
|
3178
3327
|
let readBack = 0;
|
|
3179
3328
|
let missing = 0;
|
|
3180
3329
|
const seen = /* @__PURE__ */ new Map();
|
|
3181
|
-
const
|
|
3182
|
-
|
|
3330
|
+
const READ_BACK_PAGE = 500;
|
|
3331
|
+
for (let offset = 0; ; offset += READ_BACK_PAGE) {
|
|
3332
|
+
const reread = await client.list({ table_id: tableId, limit: READ_BACK_PAGE, offset });
|
|
3333
|
+
if (!reread.ok) {
|
|
3334
|
+
refusals.push(
|
|
3335
|
+
`The rows were written and this could not read them back to check: ${reread.error.message}`
|
|
3336
|
+
);
|
|
3337
|
+
break;
|
|
3338
|
+
}
|
|
3183
3339
|
for (const row of reread.data.rows) {
|
|
3184
3340
|
seen.set(row.id, row.document ?? {});
|
|
3185
3341
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
`The rows were written and this could not read them back to check: ${reread.error.message}`
|
|
3189
|
-
);
|
|
3342
|
+
if (reread.data.rows.length < READ_BACK_PAGE) break;
|
|
3343
|
+
if (expected.every((e) => seen.has(e.recordId))) break;
|
|
3190
3344
|
}
|
|
3191
3345
|
for (const entry of expected) {
|
|
3192
3346
|
const stored = seen.get(entry.recordId);
|
|
@@ -3459,6 +3613,7 @@ function Grid({
|
|
|
3459
3613
|
noun: table.data?.label_singular ?? "record",
|
|
3460
3614
|
nounPlural: table.data?.label_plural ?? `${table.data?.label_singular ?? "record"}s`,
|
|
3461
3615
|
onConfirm: applyPaste,
|
|
3616
|
+
onAddOptions: addPastedOptions,
|
|
3462
3617
|
onCancel: () => setPastePlan(null)
|
|
3463
3618
|
}
|
|
3464
3619
|
) : null,
|
|
@@ -8955,7 +9110,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
8955
9110
|
const all = tables.data ?? [];
|
|
8956
9111
|
const missingTie = ticked.filter((e) => e.namesVia.trim() === "");
|
|
8957
9112
|
const ready = clientTableId !== null && ticked.length > 0 && missingTie.length === 0;
|
|
8958
|
-
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { className: (0, import_design_system30.cn)("flex flex-col gap-3", className), children: [
|
|
9113
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("section", { className: (0, import_design_system30.cn)("@container flex flex-col gap-3", className), children: [
|
|
8959
9114
|
/* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("header", { className: "flex items-center gap-2", children: [
|
|
8960
9115
|
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h3", { className: "text-sm font-medium", children: existing ? "Portal" : "New client portal" }),
|
|
8961
9116
|
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "flex-1" }),
|
|
@@ -9096,7 +9251,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
9096
9251
|
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
9097
9252
|
import_design_system30.BasicInput,
|
|
9098
9253
|
{
|
|
9099
|
-
className: "h-8 w-56",
|
|
9254
|
+
className: "h-8 w-full @md:w-56",
|
|
9100
9255
|
value: inviteEmail,
|
|
9101
9256
|
onChange: (e) => setInviteEmail(e.target.value)
|
|
9102
9257
|
}
|
|
@@ -9107,7 +9262,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
9107
9262
|
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
9108
9263
|
import_design_system30.BasicInput,
|
|
9109
9264
|
{
|
|
9110
|
-
className: "h-8 w-80",
|
|
9265
|
+
className: "h-8 w-full @md:w-80",
|
|
9111
9266
|
placeholder: "the record id",
|
|
9112
9267
|
value: inviteRecordId,
|
|
9113
9268
|
onChange: (e) => setInviteRecordId(e.target.value)
|
|
@@ -9133,7 +9288,7 @@ function PortalBuilder({ tableId, portalId, onSaved, onClose, className }) {
|
|
|
9133
9288
|
// src/PortalsPanel.tsx
|
|
9134
9289
|
var import_react65 = require("react");
|
|
9135
9290
|
var import_react66 = require("@ai-matrx/records/react");
|
|
9136
|
-
var
|
|
9291
|
+
var import_records6 = require("@ai-matrx/records");
|
|
9137
9292
|
var import_design_system32 = require("@ai-matrx/design-system");
|
|
9138
9293
|
|
|
9139
9294
|
// src/BuildOrAsk.tsx
|
|
@@ -9250,7 +9405,7 @@ function PortalsPanel({ tableId, className }) {
|
|
|
9250
9405
|
}
|
|
9251
9406
|
) : null,
|
|
9252
9407
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "flex flex-col gap-2", children: portals.map((portal) => {
|
|
9253
|
-
const url = `${origin}${(0,
|
|
9408
|
+
const url = `${origin}${(0, import_records6.portalPath)(portal.slug)}`;
|
|
9254
9409
|
const open = openId === portal.portal_id;
|
|
9255
9410
|
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { className: "rounded-md border border-border bg-card p-2.5", children: [
|
|
9256
9411
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
@@ -9804,7 +9959,7 @@ function DigestScheduler({
|
|
|
9804
9959
|
setPreview(answered.data);
|
|
9805
9960
|
}
|
|
9806
9961
|
if (views === null && !error) return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Skeleton, { className: (0, import_design_system33.cn)("h-56 w-full", className) });
|
|
9807
|
-
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: (0, import_design_system33.cn)("flex flex-col gap-3", className), children: [
|
|
9962
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: (0, import_design_system33.cn)("@container flex flex-col gap-3", className), children: [
|
|
9808
9963
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("header", { className: "flex items-center gap-2", children: [
|
|
9809
9964
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h3", { className: "text-sm font-medium", children: "Send this on a schedule" }),
|
|
9810
9965
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex-1" }),
|
|
@@ -9839,7 +9994,7 @@ function DigestScheduler({
|
|
|
9839
9994
|
),
|
|
9840
9995
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "text-xs text-muted-foreground", children: "The first choice saves what is on screen as a view, so the summary and the screen stay the same thing." })
|
|
9841
9996
|
] }),
|
|
9842
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "grid grid-cols-
|
|
9997
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "grid grid-cols-1 gap-2 @md:grid-cols-2", children: [
|
|
9843
9998
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
9844
9999
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_design_system33.Label, { className: "text-xs font-medium", children: "How often" }),
|
|
9845
10000
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
@@ -10154,7 +10309,7 @@ function SubscriptionsPanel({ tableId, className }) {
|
|
|
10154
10309
|
// src/FormBuilder.tsx
|
|
10155
10310
|
var import_react73 = require("react");
|
|
10156
10311
|
var import_react74 = require("@ai-matrx/records/react");
|
|
10157
|
-
var
|
|
10312
|
+
var import_records7 = require("@ai-matrx/records");
|
|
10158
10313
|
var import_design_system36 = require("@ai-matrx/design-system");
|
|
10159
10314
|
|
|
10160
10315
|
// src/FormRunner.tsx
|
|
@@ -10580,7 +10735,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
10580
10735
|
if (!rights.admin) {
|
|
10581
10736
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)("p", { className: (0, import_design_system36.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
10582
10737
|
}
|
|
10583
|
-
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: (0, import_design_system36.cn)("flex min-h-0 gap-3", className), children: [
|
|
10738
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: (0, import_design_system36.cn)("@container flex min-h-0 flex-col gap-3 @3xl:flex-row", className), children: [
|
|
10584
10739
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-2 overflow-auto", children: [
|
|
10585
10740
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Forms", children: [
|
|
10586
10741
|
forms.map((form) => /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
@@ -10607,7 +10762,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
10607
10762
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
10608
10763
|
PublishRow,
|
|
10609
10764
|
{
|
|
10610
|
-
url: `${publicOrigin}${(0,
|
|
10765
|
+
url: `${publicOrigin}${(0, import_records7.publicFormPath)(draft.id)}`,
|
|
10611
10766
|
state: draft.state,
|
|
10612
10767
|
mayPublish: rights.structure,
|
|
10613
10768
|
why: rights.why("structure"),
|
|
@@ -10741,7 +10896,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, className }) {
|
|
|
10741
10896
|
] })
|
|
10742
10897
|
] })
|
|
10743
10898
|
] }),
|
|
10744
|
-
draft ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("aside", { className: "w-
|
|
10899
|
+
draft ? /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("aside", { className: "w-full shrink-0 overflow-auto rounded border p-2 @3xl:w-96", children: [
|
|
10745
10900
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("h4", { className: "px-1 text-sm font-medium", children: draft.name }),
|
|
10746
10901
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(FormRunner, { form: draft, preview: true })
|
|
10747
10902
|
] }) : null
|
|
@@ -11672,7 +11827,7 @@ function NotifyRuleEditor({ tableId, seed, className }) {
|
|
|
11672
11827
|
// src/ChartBlock.tsx
|
|
11673
11828
|
var import_react85 = require("react");
|
|
11674
11829
|
var import_recharts = require("recharts");
|
|
11675
|
-
var
|
|
11830
|
+
var import_records8 = require("@ai-matrx/records");
|
|
11676
11831
|
var import_core6 = require("@ai-matrx/records/core");
|
|
11677
11832
|
var import_design_system42 = require("@ai-matrx/design-system");
|
|
11678
11833
|
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
@@ -11693,7 +11848,7 @@ function ChartBlock({ block, subject, className }) {
|
|
|
11693
11848
|
const kind = block.kind;
|
|
11694
11849
|
const tableId = block.table_id ?? subject;
|
|
11695
11850
|
const measures = block.measures && block.measures.length > 0 ? block.measures : [{ op: "count" }];
|
|
11696
|
-
const primary = (0,
|
|
11851
|
+
const primary = (0, import_records8.measureKey)(measures[0]);
|
|
11697
11852
|
const series = [primary];
|
|
11698
11853
|
const points = (0, import_react85.useMemo)(() => {
|
|
11699
11854
|
if (kind === "stuck") return [];
|
|
@@ -12227,7 +12382,7 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
12227
12382
|
// src/FormsPanel.tsx
|
|
12228
12383
|
var import_react88 = require("react");
|
|
12229
12384
|
var import_react89 = require("@ai-matrx/records/react");
|
|
12230
|
-
var
|
|
12385
|
+
var import_records9 = require("@ai-matrx/records");
|
|
12231
12386
|
var import_design_system44 = require("@ai-matrx/design-system");
|
|
12232
12387
|
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
12233
12388
|
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.";
|
|
@@ -12322,7 +12477,7 @@ function FormsPanel({ tableId, className }) {
|
|
|
12322
12477
|
}
|
|
12323
12478
|
) : null,
|
|
12324
12479
|
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("ul", { className: "flex flex-col gap-2", children: forms.map((form) => {
|
|
12325
|
-
const url = `${origin}${(0,
|
|
12480
|
+
const url = `${origin}${(0, import_records9.publicFormPath)(form.form_id)}`;
|
|
12326
12481
|
return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("li", { className: "rounded-md border p-2.5", children: [
|
|
12327
12482
|
/* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
12328
12483
|
/* @__PURE__ */ (0, import_jsx_runtime47.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: form.title ?? form.slug }),
|
|
@@ -12374,7 +12529,7 @@ function FormsPanel({ tableId, className }) {
|
|
|
12374
12529
|
// src/BookingBuilder.tsx
|
|
12375
12530
|
var import_react90 = require("react");
|
|
12376
12531
|
var import_react91 = require("@ai-matrx/records/react");
|
|
12377
|
-
var
|
|
12532
|
+
var import_records10 = require("@ai-matrx/records");
|
|
12378
12533
|
var import_design_system45 = require("@ai-matrx/design-system");
|
|
12379
12534
|
var import_jsx_runtime48 = require("react/jsx-runtime");
|
|
12380
12535
|
var STORE_ANSWERS_THESE = ["slot", "status", "booked_with"];
|
|
@@ -12505,203 +12660,208 @@ function BookingBuilder({ tableId, bookingId, onSaved, onClose, className }) {
|
|
|
12505
12660
|
if (!rights.structure) {
|
|
12506
12661
|
return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: (0, import_design_system45.cn)("text-xs text-muted-foreground", className), children: rights.why("structure") ?? "Making a booking page needs the admin level on this table, because it lets people with no account take time in your calendar." });
|
|
12507
12662
|
}
|
|
12508
|
-
const url = formId ? `${publicOrigin}${(0,
|
|
12663
|
+
const url = formId ? `${publicOrigin}${(0, import_records10.bookingPath)(formId)}` : null;
|
|
12509
12664
|
const shown = offer ?? null;
|
|
12510
|
-
return
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.BasicInput, { value: title, onChange: (e) => setTitle(e.target.value) })
|
|
12521
|
-
] }),
|
|
12522
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
12523
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12524
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "How long one appointment is" }),
|
|
12525
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12526
|
-
"select",
|
|
12527
|
-
{
|
|
12528
|
-
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12529
|
-
value: minutes,
|
|
12530
|
-
onChange: (e) => setMinutes(Number(e.target.value)),
|
|
12531
|
-
children: LENGTHS.map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("option", { value: n, children: [
|
|
12532
|
-
n,
|
|
12533
|
-
" minutes"
|
|
12534
|
-
] }, n))
|
|
12535
|
-
}
|
|
12536
|
-
)
|
|
12537
|
-
] }),
|
|
12538
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12539
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Gap kept after each one" }),
|
|
12540
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12541
|
-
"select",
|
|
12542
|
-
{
|
|
12543
|
-
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12544
|
-
value: buffer,
|
|
12545
|
-
onChange: (e) => setBuffer(Number(e.target.value)),
|
|
12546
|
-
children: [0, 5, 10, 15, 30].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("option", { value: n, children: n === 0 ? "No gap \u2014 back to back" : `${n} minutes` }, n))
|
|
12547
|
-
}
|
|
12548
|
-
)
|
|
12549
|
-
] }),
|
|
12550
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12551
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Earliest somebody may book" }),
|
|
12552
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12553
|
-
"select",
|
|
12554
|
-
{
|
|
12555
|
-
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12556
|
-
value: lead,
|
|
12557
|
-
onChange: (e) => setLead(Number(e.target.value)),
|
|
12558
|
-
children: [0, 60, 120, 240, 1440].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("option", { value: n, children: n === 0 ? "Right away" : n === 1440 ? "A day from now" : `${n / 60} hour${n === 60 ? "" : "s"} from now` }, n))
|
|
12559
|
-
}
|
|
12560
|
-
)
|
|
12665
|
+
return (
|
|
12666
|
+
// THE RAIL IS 384px WIDE AND THE VIEWPORT IS NOT (walk 1, 2026-09-21):
|
|
12667
|
+
// every split below is a CONTAINER query, so this screen is the same
|
|
12668
|
+
// screen in a rail, a dialog and a full page.
|
|
12669
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("section", { className: (0, import_design_system45.cn)("@container flex flex-col gap-3", className), children: [
|
|
12670
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("header", { className: "flex items-center gap-2", children: [
|
|
12671
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("h3", { className: "text-sm font-medium", children: existing ? "Booking page" : "New booking page" }),
|
|
12672
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex-1" }),
|
|
12673
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Button, { size: "sm", disabled: saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" }),
|
|
12674
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Button, { size: "sm", variant: "ghost", onClick: onClose, children: "Close" }) : null
|
|
12561
12675
|
] }),
|
|
12676
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(RefusalNotice, { error }) : null,
|
|
12562
12677
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12563
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "
|
|
12564
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12565
|
-
import_design_system45.BasicInput,
|
|
12566
|
-
{
|
|
12567
|
-
type: "number",
|
|
12568
|
-
min: 1,
|
|
12569
|
-
value: perDay,
|
|
12570
|
-
onChange: (e) => setPerDay(Math.max(1, Number(e.target.value) || 1))
|
|
12571
|
-
}
|
|
12572
|
-
)
|
|
12678
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "What it is called" }),
|
|
12679
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.BasicInput, { value: title, onChange: (e) => setTitle(e.target.value) })
|
|
12573
12680
|
] }),
|
|
12574
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("
|
|
12575
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.
|
|
12576
|
-
|
|
12577
|
-
"select",
|
|
12578
|
-
{
|
|
12579
|
-
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12580
|
-
value: days,
|
|
12581
|
-
onChange: (e) => setDays(Number(e.target.value)),
|
|
12582
|
-
children: [7, 14, 30, 60, 90].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("option", { value: n, children: [
|
|
12583
|
-
n,
|
|
12584
|
-
" days"
|
|
12585
|
-
] }, n))
|
|
12586
|
-
}
|
|
12587
|
-
)
|
|
12588
|
-
] })
|
|
12589
|
-
] }),
|
|
12590
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
12591
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Hours you are free" }),
|
|
12592
|
-
windows.map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
12593
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex w-20 items-center gap-1.5 text-xs", children: [
|
|
12681
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "grid grid-cols-1 gap-2 @md:grid-cols-2", children: [
|
|
12682
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12683
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "How long one appointment is" }),
|
|
12594
12684
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12595
|
-
|
|
12685
|
+
"select",
|
|
12596
12686
|
{
|
|
12597
|
-
|
|
12598
|
-
|
|
12687
|
+
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12688
|
+
value: minutes,
|
|
12689
|
+
onChange: (e) => setMinutes(Number(e.target.value)),
|
|
12690
|
+
children: LENGTHS.map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("option", { value: n, children: [
|
|
12691
|
+
n,
|
|
12692
|
+
" minutes"
|
|
12693
|
+
] }, n))
|
|
12599
12694
|
}
|
|
12600
|
-
)
|
|
12601
|
-
DAYS[i]?.label
|
|
12695
|
+
)
|
|
12602
12696
|
] }),
|
|
12603
|
-
|
|
12697
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12698
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Gap kept after each one" }),
|
|
12604
12699
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12605
|
-
|
|
12700
|
+
"select",
|
|
12606
12701
|
{
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12702
|
+
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12703
|
+
value: buffer,
|
|
12704
|
+
onChange: (e) => setBuffer(Number(e.target.value)),
|
|
12705
|
+
children: [0, 5, 10, 15, 30].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("option", { value: n, children: n === 0 ? "No gap \u2014 back to back" : `${n} minutes` }, n))
|
|
12611
12706
|
}
|
|
12612
|
-
)
|
|
12613
|
-
|
|
12707
|
+
)
|
|
12708
|
+
] }),
|
|
12709
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12710
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Earliest somebody may book" }),
|
|
12711
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12712
|
+
"select",
|
|
12713
|
+
{
|
|
12714
|
+
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12715
|
+
value: lead,
|
|
12716
|
+
onChange: (e) => setLead(Number(e.target.value)),
|
|
12717
|
+
children: [0, 60, 120, 240, 1440].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("option", { value: n, children: n === 0 ? "Right away" : n === 1440 ? "A day from now" : `${n / 60} hour${n === 60 ? "" : "s"} from now` }, n))
|
|
12718
|
+
}
|
|
12719
|
+
)
|
|
12720
|
+
] }),
|
|
12721
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12722
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Most in one day" }),
|
|
12614
12723
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12615
12724
|
import_design_system45.BasicInput,
|
|
12616
12725
|
{
|
|
12617
|
-
type: "
|
|
12618
|
-
|
|
12619
|
-
value:
|
|
12620
|
-
onChange: (e) =>
|
|
12726
|
+
type: "number",
|
|
12727
|
+
min: 1,
|
|
12728
|
+
value: perDay,
|
|
12729
|
+
onChange: (e) => setPerDay(Math.max(1, Number(e.target.value) || 1))
|
|
12621
12730
|
}
|
|
12622
12731
|
)
|
|
12623
|
-
] })
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12732
|
+
] }),
|
|
12733
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12734
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "How far ahead the page offers" }),
|
|
12735
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12736
|
+
"select",
|
|
12737
|
+
{
|
|
12738
|
+
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
12739
|
+
value: days,
|
|
12740
|
+
onChange: (e) => setDays(Number(e.target.value)),
|
|
12741
|
+
children: [7, 14, 30, 60, 90].map((n) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("option", { value: n, children: [
|
|
12742
|
+
n,
|
|
12743
|
+
" days"
|
|
12744
|
+
] }, n))
|
|
12745
|
+
}
|
|
12746
|
+
)
|
|
12747
|
+
] })
|
|
12748
|
+
] }),
|
|
12749
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
12750
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "Hours you are free" }),
|
|
12751
|
+
windows.map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
12752
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex w-20 items-center gap-1.5 text-xs", children: [
|
|
12753
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12754
|
+
import_design_system45.Checkbox,
|
|
12755
|
+
{
|
|
12756
|
+
checked: w.on,
|
|
12757
|
+
onCheckedChange: (on) => setWindows((prev) => prev.map((x, j) => i === j ? { ...x, on: Boolean(on) } : x))
|
|
12758
|
+
}
|
|
12759
|
+
),
|
|
12760
|
+
DAYS[i]?.label
|
|
12761
|
+
] }),
|
|
12762
|
+
w.on ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
|
|
12763
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12764
|
+
import_design_system45.BasicInput,
|
|
12765
|
+
{
|
|
12766
|
+
type: "time",
|
|
12767
|
+
className: "h-8 w-28",
|
|
12768
|
+
value: w.from,
|
|
12769
|
+
onChange: (e) => setWindows((prev) => prev.map((x, j) => i === j ? { ...x, from: e.target.value } : x))
|
|
12770
|
+
}
|
|
12771
|
+
),
|
|
12772
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
12773
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12774
|
+
import_design_system45.BasicInput,
|
|
12775
|
+
{
|
|
12776
|
+
type: "time",
|
|
12777
|
+
className: "h-8 w-28",
|
|
12778
|
+
value: w.to,
|
|
12779
|
+
onChange: (e) => setWindows((prev) => prev.map((x, j) => i === j ? { ...x, to: e.target.value } : x))
|
|
12780
|
+
}
|
|
12781
|
+
)
|
|
12782
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "text-xs text-muted-foreground", children: "not taking bookings" })
|
|
12783
|
+
] }, w.weekday))
|
|
12784
|
+
] }),
|
|
12785
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
|
|
12786
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "What to ask the person booking" }),
|
|
12787
|
+
askable.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-xs text-muted-foreground", children: "This table has no fields yet, so there is nothing a booking page could ask for. Add a field first \u2014 every question is one of this table's own fields." }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { className: "flex flex-wrap gap-x-4 gap-y-1.5", children: askable.map((f) => /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex items-center gap-1.5 text-xs", children: [
|
|
12788
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12789
|
+
import_design_system45.Checkbox,
|
|
12790
|
+
{
|
|
12791
|
+
checked: asked.includes(f.key),
|
|
12792
|
+
onCheckedChange: (on) => setAsked((prev) => on ? [...prev, f.key] : prev.filter((k) => k !== f.key))
|
|
12793
|
+
}
|
|
12794
|
+
),
|
|
12795
|
+
fieldName(f)
|
|
12796
|
+
] }, f.key)) }),
|
|
12797
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "text-xs text-muted-foreground", children: "The time, whether it is booked or cancelled, and who it is with are filled in by the store \u2014 a booking page cannot ask a visitor for any of the three." })
|
|
12798
|
+
] }),
|
|
12799
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("label", { className: "flex flex-col gap-1", children: [
|
|
12800
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system45.Label, { className: "text-xs font-medium", children: "What they see after booking" }),
|
|
12629
12801
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12630
|
-
import_design_system45.
|
|
12802
|
+
import_design_system45.BasicTextarea,
|
|
12631
12803
|
{
|
|
12632
|
-
|
|
12633
|
-
|
|
12804
|
+
rows: 2,
|
|
12805
|
+
value: confirmation,
|
|
12806
|
+
placeholder: "You're booked. We'll send a reminder the day before \u2014 reply to that message to move it.",
|
|
12807
|
+
onChange: (e) => setConfirmation(e.target.value)
|
|
12808
|
+
}
|
|
12809
|
+
)
|
|
12810
|
+
] }),
|
|
12811
|
+
shown ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
|
|
12812
|
+
"Saved. The page offers ",
|
|
12813
|
+
shown.slot_minutes,
|
|
12814
|
+
"-minute appointments in ",
|
|
12815
|
+
shown.timezone,
|
|
12816
|
+
", up to",
|
|
12817
|
+
" ",
|
|
12818
|
+
shown.max_per_day,
|
|
12819
|
+
" a day, ",
|
|
12820
|
+
shown.days,
|
|
12821
|
+
" days ahead",
|
|
12822
|
+
shown.buffer_minutes > 0 ? `, with ${shown.buffer_minutes} minutes between them` : "",
|
|
12823
|
+
":",
|
|
12824
|
+
" ",
|
|
12825
|
+
shown.windows.map(
|
|
12826
|
+
(w) => `${DAYS.find((d) => d.weekday === w.weekday)?.label ?? w.weekday} ${w.from}\u2013${w.to}`
|
|
12827
|
+
).join(", "),
|
|
12828
|
+
"."
|
|
12829
|
+
] }) : null,
|
|
12830
|
+
url ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded-md border p-2.5", children: [
|
|
12831
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 flex-1 break-all text-xs", children: url }),
|
|
12832
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12833
|
+
import_design_system45.Button,
|
|
12834
|
+
{
|
|
12835
|
+
size: "sm",
|
|
12836
|
+
variant: "outline",
|
|
12837
|
+
onClick: () => {
|
|
12838
|
+
void navigator.clipboard.writeText(url).then(() => {
|
|
12839
|
+
setCopied(true);
|
|
12840
|
+
window.setTimeout(() => setCopied(false), 1600);
|
|
12841
|
+
}).catch(() => setCopied(false));
|
|
12842
|
+
},
|
|
12843
|
+
children: copied ? "Copied" : "Copy link"
|
|
12634
12844
|
}
|
|
12635
12845
|
),
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
}
|
|
12650
|
-
)
|
|
12651
|
-
] }),
|
|
12652
|
-
shown ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("p", { className: "rounded-md border bg-muted/40 p-2.5 text-xs", children: [
|
|
12653
|
-
"Saved. The page offers ",
|
|
12654
|
-
shown.slot_minutes,
|
|
12655
|
-
"-minute appointments in ",
|
|
12656
|
-
shown.timezone,
|
|
12657
|
-
", up to",
|
|
12658
|
-
" ",
|
|
12659
|
-
shown.max_per_day,
|
|
12660
|
-
" a day, ",
|
|
12661
|
-
shown.days,
|
|
12662
|
-
" days ahead",
|
|
12663
|
-
shown.buffer_minutes > 0 ? `, with ${shown.buffer_minutes} minutes between them` : "",
|
|
12664
|
-
":",
|
|
12665
|
-
" ",
|
|
12666
|
-
shown.windows.map(
|
|
12667
|
-
(w) => `${DAYS.find((d) => d.weekday === w.weekday)?.label ?? w.weekday} ${w.from}\u2013${w.to}`
|
|
12668
|
-
).join(", "),
|
|
12669
|
-
"."
|
|
12670
|
-
] }) : null,
|
|
12671
|
-
url ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded-md border p-2.5", children: [
|
|
12672
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "min-w-0 flex-1 break-all text-xs", children: url }),
|
|
12673
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12674
|
-
import_design_system45.Button,
|
|
12675
|
-
{
|
|
12676
|
-
size: "sm",
|
|
12677
|
-
variant: "outline",
|
|
12678
|
-
onClick: () => {
|
|
12679
|
-
void navigator.clipboard.writeText(url).then(() => {
|
|
12680
|
-
setCopied(true);
|
|
12681
|
-
window.setTimeout(() => setCopied(false), 1600);
|
|
12682
|
-
}).catch(() => setCopied(false));
|
|
12683
|
-
},
|
|
12684
|
-
children: copied ? "Copied" : "Copy link"
|
|
12685
|
-
}
|
|
12686
|
-
),
|
|
12687
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12688
|
-
import_design_system45.Button,
|
|
12689
|
-
{
|
|
12690
|
-
size: "sm",
|
|
12691
|
-
disabled: publishing,
|
|
12692
|
-
onClick: () => void publish(!(existing?.published_at && !existing.closed_at)),
|
|
12693
|
-
children: publishing ? "\u2026" : existing?.published_at && !existing.closed_at ? "Unpublish" : "Publish"
|
|
12694
|
-
}
|
|
12695
|
-
),
|
|
12696
|
-
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "w-full text-xs text-muted-foreground", children: existing?.published_at && !existing.closed_at ? "Open. Anyone with this link can book a time." : "Not open yet \u2014 the link does not take bookings until you publish it." })
|
|
12697
|
-
] }) : null
|
|
12698
|
-
] });
|
|
12846
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
|
|
12847
|
+
import_design_system45.Button,
|
|
12848
|
+
{
|
|
12849
|
+
size: "sm",
|
|
12850
|
+
disabled: publishing,
|
|
12851
|
+
onClick: () => void publish(!(existing?.published_at && !existing.closed_at)),
|
|
12852
|
+
children: publishing ? "\u2026" : existing?.published_at && !existing.closed_at ? "Unpublish" : "Publish"
|
|
12853
|
+
}
|
|
12854
|
+
),
|
|
12855
|
+
/* @__PURE__ */ (0, import_jsx_runtime48.jsx)("p", { className: "w-full text-xs text-muted-foreground", children: existing?.published_at && !existing.closed_at ? "Open. Anyone with this link can book a time." : "Not open yet \u2014 the link does not take bookings until you publish it." })
|
|
12856
|
+
] }) : null
|
|
12857
|
+
] })
|
|
12858
|
+
);
|
|
12699
12859
|
}
|
|
12700
12860
|
|
|
12701
12861
|
// src/BookingSlots.tsx
|
|
12702
12862
|
var import_react92 = require("react");
|
|
12703
12863
|
var import_react93 = require("@ai-matrx/records/react");
|
|
12704
|
-
var
|
|
12864
|
+
var import_records11 = require("@ai-matrx/records");
|
|
12705
12865
|
var import_design_system46 = require("@ai-matrx/design-system");
|
|
12706
12866
|
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
12707
12867
|
var WHAT_A_BOOKING_PAGE_IS = "A booking page offers times you are free and writes each appointment into this table as an ordinary record.";
|
|
@@ -12809,7 +12969,7 @@ function BookingSlots({ tableId, className }) {
|
|
|
12809
12969
|
" Open the table the appointments should land in to make one."
|
|
12810
12970
|
] }) : null,
|
|
12811
12971
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("ul", { className: "flex flex-col gap-2", children: pages.map((page) => {
|
|
12812
|
-
const url = `${origin}${(0,
|
|
12972
|
+
const url = `${origin}${(0, import_records11.bookingPath)(page.form_id)}`;
|
|
12813
12973
|
const mayOpen = levels.data?.[page.table_id] === "admin";
|
|
12814
12974
|
const open = page.published_at !== null && page.closed_at === null;
|
|
12815
12975
|
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("li", { className: "rounded-md border p-2.5", "data-testid": "booking-page", children: [
|
|
@@ -12898,7 +13058,7 @@ var import_design_system48 = require("@ai-matrx/design-system");
|
|
|
12898
13058
|
|
|
12899
13059
|
// src/CaptureRun.tsx
|
|
12900
13060
|
var import_react94 = require("react");
|
|
12901
|
-
var
|
|
13061
|
+
var import_records12 = require("@ai-matrx/records");
|
|
12902
13062
|
var import_react95 = require("@ai-matrx/records/react");
|
|
12903
13063
|
var import_design_system47 = require("@ai-matrx/design-system");
|
|
12904
13064
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
@@ -12953,7 +13113,7 @@ function CaptureRun({ sheetId, face: given, className }) {
|
|
|
12953
13113
|
const [sending, setSending] = (0, import_react94.useState)(false);
|
|
12954
13114
|
const queueRef = (0, import_react94.useRef)(null);
|
|
12955
13115
|
(0, import_react94.useEffect)(() => {
|
|
12956
|
-
const q2 = (0,
|
|
13116
|
+
const q2 = (0, import_records12.openCaptureQueue)({
|
|
12957
13117
|
onChange: (c, all) => {
|
|
12958
13118
|
setCounts(c);
|
|
12959
13119
|
setItems(all);
|