@ai-matrx/records-ui 0.77.0 → 0.79.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 +54 -0
- package/dist/index.cjs +311 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +367 -130
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -4476,6 +4476,16 @@ var import_design_system13 = require("@ai-matrx/design-system");
|
|
|
4476
4476
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4477
4477
|
var BATCH = 250;
|
|
4478
4478
|
var SAMPLES = 40;
|
|
4479
|
+
var MAPPING_COLUMNS = "minmax(9rem, 11rem) minmax(14rem, 17rem) minmax(13rem, 1fr) minmax(8rem, 10rem)";
|
|
4480
|
+
var ABSENT = "__absent__";
|
|
4481
|
+
function obviousTitleColumn(columns) {
|
|
4482
|
+
const says = /^(title|name|record|customer|client|company|job|item|subject|label|description)\b|(\bname|\btitle|\bnumber|\bid)$/i;
|
|
4483
|
+
const named = columns.find((c) => says.test(c.header.trim()));
|
|
4484
|
+
if (named) return named.header;
|
|
4485
|
+
const text = columns.find((c) => c.type === "text");
|
|
4486
|
+
if (text) return text.header;
|
|
4487
|
+
return columns[0]?.header ?? null;
|
|
4488
|
+
}
|
|
4479
4489
|
async function sha256(bytes) {
|
|
4480
4490
|
const subtle = globalThis.crypto?.subtle;
|
|
4481
4491
|
if (!subtle) return null;
|
|
@@ -4510,6 +4520,7 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4510
4520
|
const [alreadyRan, setAlreadyRan] = (0, import_react26.useState)(null);
|
|
4511
4521
|
const [columnsAdded, setColumnsAdded] = (0, import_react26.useState)(null);
|
|
4512
4522
|
const [show, setShow] = (0, import_react26.useState)("all");
|
|
4523
|
+
const [ledger, setLedger] = (0, import_react26.useState)(null);
|
|
4513
4524
|
const declared = fields.data ?? [];
|
|
4514
4525
|
const take = (0, import_react26.useCallback)(
|
|
4515
4526
|
async (file) => {
|
|
@@ -4520,6 +4531,7 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4520
4531
|
setProposals([]);
|
|
4521
4532
|
setAlreadyRan(null);
|
|
4522
4533
|
setProgress(0);
|
|
4534
|
+
setLedger(null);
|
|
4523
4535
|
try {
|
|
4524
4536
|
const xlsx = file.name.toLowerCase().endsWith(".xlsx");
|
|
4525
4537
|
const bytes = await file.arrayBuffer();
|
|
@@ -4546,6 +4558,11 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4546
4558
|
for (const column of answered.data.columns) {
|
|
4547
4559
|
if (column.matched) next[column.header] = column.field_key;
|
|
4548
4560
|
}
|
|
4561
|
+
const titleKey2 = table.data?.title_field ?? null;
|
|
4562
|
+
if (titleKey2 && !Object.values(next).includes(titleKey2)) {
|
|
4563
|
+
const guess = obviousTitleColumn(answered.data.columns);
|
|
4564
|
+
if (guess) next[guess] = titleKey2;
|
|
4565
|
+
}
|
|
4549
4566
|
setMapping(next);
|
|
4550
4567
|
setPhase("ready");
|
|
4551
4568
|
} catch (thrown) {
|
|
@@ -4553,7 +4570,7 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4553
4570
|
setPhase("waiting");
|
|
4554
4571
|
}
|
|
4555
4572
|
},
|
|
4556
|
-
[client, declared, tableId]
|
|
4573
|
+
[client, declared, tableId, table.data?.title_field]
|
|
4557
4574
|
);
|
|
4558
4575
|
const run = (0, import_react26.useCallback)(async () => {
|
|
4559
4576
|
if (!plan) return;
|
|
@@ -4561,6 +4578,7 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4561
4578
|
setOutcomes([]);
|
|
4562
4579
|
setColumnsAdded(null);
|
|
4563
4580
|
setProgress(0);
|
|
4581
|
+
setLedger(null);
|
|
4564
4582
|
if (unmapped === "create" && rights.admin) {
|
|
4565
4583
|
setPhase("declaring");
|
|
4566
4584
|
const made = await client.importDeclareColumns({ table_id: tableId, rows, mapping });
|
|
@@ -4596,6 +4614,7 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4596
4614
|
}
|
|
4597
4615
|
const importId = opened.data.import_id;
|
|
4598
4616
|
const collected = [];
|
|
4617
|
+
const told = { seen: 0, written: 0, duplicate: 0, refused: 0 };
|
|
4599
4618
|
let take2 = opened.data.rows_per_call ?? BATCH;
|
|
4600
4619
|
let at = 0;
|
|
4601
4620
|
while (at < rows.length) {
|
|
@@ -4604,11 +4623,17 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4604
4623
|
if (!answered.ok) {
|
|
4605
4624
|
setProblem(answered.error.message);
|
|
4606
4625
|
setOutcomes([...collected]);
|
|
4626
|
+
setLedger({ ...told, final: false });
|
|
4607
4627
|
setPhase("ready");
|
|
4608
4628
|
return;
|
|
4609
4629
|
}
|
|
4610
4630
|
collected.push(...answered.data.outcomes);
|
|
4611
4631
|
setOutcomes([...collected]);
|
|
4632
|
+
told.seen += answered.data.rows_seen;
|
|
4633
|
+
told.written += answered.data.rows_written;
|
|
4634
|
+
told.duplicate += answered.data.rows_duplicate;
|
|
4635
|
+
told.refused += answered.data.rows_refused;
|
|
4636
|
+
setLedger({ ...told, final: false });
|
|
4612
4637
|
at += batch.length;
|
|
4613
4638
|
setProgress(Math.min(at, rows.length));
|
|
4614
4639
|
if (answered.data.rows_per_call) take2 = answered.data.rows_per_call;
|
|
@@ -4621,6 +4646,13 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4621
4646
|
}
|
|
4622
4647
|
setSummary(finished.data.message);
|
|
4623
4648
|
setProposals(finished.data.proposals);
|
|
4649
|
+
setLedger({
|
|
4650
|
+
seen: finished.data.rows_seen,
|
|
4651
|
+
written: finished.data.rows_written,
|
|
4652
|
+
duplicate: finished.data.rows_duplicate,
|
|
4653
|
+
refused: finished.data.rows_refused,
|
|
4654
|
+
final: true
|
|
4655
|
+
});
|
|
4624
4656
|
setPhase("done");
|
|
4625
4657
|
onDone?.(finished.data.rows_written);
|
|
4626
4658
|
}, [client, dedupeKey, fileBytes, fileHash, fileName, format, mapping, onDuplicate, onDone, plan, rows, tableId, unmapped]);
|
|
@@ -4640,6 +4672,10 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4640
4672
|
[outcomes, show]
|
|
4641
4673
|
);
|
|
4642
4674
|
const mappedCount = Object.keys(mapping).length;
|
|
4675
|
+
const titleKey = table.data?.title_field ?? null;
|
|
4676
|
+
const titleMappedTo = titleKey ? Object.entries(mapping).find(([, key]) => key === titleKey)?.[0] ?? null : null;
|
|
4677
|
+
const titleMissing = Boolean(titleKey) && titleMappedTo === null;
|
|
4678
|
+
const titleFieldLabel = (titleKey ? plan?.fields.find((f) => f.key === titleKey)?.label : null) || (titleKey ? declared.find((f) => f.key === titleKey)?.label : null) || "the name column";
|
|
4643
4679
|
const busy = phase === "reading" || phase === "planning" || phase === "declaring" || phase === "writing";
|
|
4644
4680
|
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: [
|
|
4645
4681
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
@@ -4672,55 +4708,107 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4672
4708
|
phase === "reading" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Reading the file\u2026" }) : null,
|
|
4673
4709
|
phase === "planning" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: "Working out what each column is\u2026" }) : null,
|
|
4674
4710
|
plan && parsed ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4675
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
4676
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4711
|
+
titleKey ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded border bg-muted/40 px-2 py-1.5", children: [
|
|
4712
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium", children: "What each record is called" }),
|
|
4713
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4714
|
+
import_design_system13.Select,
|
|
4715
|
+
{
|
|
4716
|
+
value: titleMappedTo ?? "",
|
|
4717
|
+
disabled: busy,
|
|
4718
|
+
onValueChange: (chosen) => {
|
|
4719
|
+
setMapping((m) => {
|
|
4720
|
+
const next = { ...m };
|
|
4721
|
+
for (const [header, key] of Object.entries(next)) {
|
|
4722
|
+
if (key === titleKey) delete next[header];
|
|
4723
|
+
}
|
|
4724
|
+
if (chosen) next[chosen] = titleKey;
|
|
4725
|
+
return next;
|
|
4726
|
+
});
|
|
4727
|
+
},
|
|
4728
|
+
children: [
|
|
4729
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4730
|
+
import_design_system13.SelectTrigger,
|
|
4731
|
+
{
|
|
4732
|
+
size: "sm",
|
|
4733
|
+
"aria-label": "Which column each record is named by",
|
|
4734
|
+
className: "min-w-[14rem]",
|
|
4735
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectValue, { placeholder: "\u2014 choose the column \u2014" })
|
|
4736
|
+
}
|
|
4737
|
+
),
|
|
4738
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectContent, { children: plan.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectItem, { value: c.header, children: c.header }, c.header)) })
|
|
4739
|
+
]
|
|
4740
|
+
}
|
|
4741
|
+
),
|
|
4742
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-muted-foreground", children: titleMappedTo ? `goes into ${titleFieldLabel}, so every record this file makes has a name you can read in the grid and on a chip.` : `Nothing goes into ${titleFieldLabel} yet, so every record this file makes would land with no name at all. Pick the column that says what each row IS.` })
|
|
4743
|
+
] }) : null,
|
|
4744
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "w-full overflow-x-auto rounded border", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-[52rem]", children: [
|
|
4745
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4746
|
+
"div",
|
|
4747
|
+
{
|
|
4748
|
+
className: "grid items-center gap-x-3 border-b bg-muted px-2 py-1 font-medium",
|
|
4749
|
+
style: { gridTemplateColumns: MAPPING_COLUMNS },
|
|
4750
|
+
children: [
|
|
4751
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Column in the file" }),
|
|
4752
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Goes to" }),
|
|
4753
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "Kind" }),
|
|
4754
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: "First values" })
|
|
4755
|
+
]
|
|
4756
|
+
}
|
|
4757
|
+
),
|
|
4758
|
+
plan.columns.map((column) => {
|
|
4689
4759
|
const to = mapping[column.header] ?? "";
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4760
|
+
const absent = unmapped === "ignore" ? "\u2014 leave it out \u2014" : "\u2014 offer it as a new column \u2014";
|
|
4761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4762
|
+
"div",
|
|
4763
|
+
{
|
|
4764
|
+
className: "grid items-start gap-x-3 border-t px-2 py-1",
|
|
4765
|
+
style: { gridTemplateColumns: MAPPING_COLUMNS },
|
|
4766
|
+
children: [
|
|
4767
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate pt-1.5 font-medium", title: column.header, children: column.header }),
|
|
4768
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4769
|
+
import_design_system13.Select,
|
|
4770
|
+
{
|
|
4771
|
+
value: to || ABSENT,
|
|
4772
|
+
disabled: busy,
|
|
4773
|
+
onValueChange: (chosen) => {
|
|
4774
|
+
setMapping((m) => {
|
|
4775
|
+
const next = { ...m };
|
|
4776
|
+
if (chosen === ABSENT) delete next[column.header];
|
|
4777
|
+
else next[column.header] = chosen;
|
|
4778
|
+
return next;
|
|
4779
|
+
});
|
|
4780
|
+
},
|
|
4781
|
+
children: [
|
|
4782
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4783
|
+
import_design_system13.SelectTrigger,
|
|
4784
|
+
{
|
|
4785
|
+
size: "sm",
|
|
4786
|
+
"aria-label": `Where ${column.header} goes`,
|
|
4787
|
+
"data-goes-to": column.header,
|
|
4788
|
+
className: "w-full min-w-[14rem]",
|
|
4789
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectValue, {})
|
|
4790
|
+
}
|
|
4791
|
+
),
|
|
4792
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_design_system13.SelectContent, { children: [
|
|
4793
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectItem, { value: ABSENT, children: absent }),
|
|
4794
|
+
plan.fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.SelectItem, { value: f.key, children: f.label || f.key }, f.field_id))
|
|
4795
|
+
] })
|
|
4796
|
+
]
|
|
4797
|
+
}
|
|
4798
|
+
),
|
|
4799
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pt-1", children: [
|
|
4800
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Badge, { variant: column.matched ? "secondary" : "outline", className: "text-[11px] font-normal", children: typeWord(column.type) }),
|
|
4801
|
+
column.unit ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "ml-1 opacity-60", children: column.unit }) : null,
|
|
4802
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "mt-0.5 text-muted-foreground", children: column.why }),
|
|
4803
|
+
column.ambiguous ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-destructive", children: "Check a date you know before you run this." }) : null,
|
|
4804
|
+
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
|
|
4805
|
+
] }),
|
|
4806
|
+
/* @__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 ") })
|
|
4807
|
+
]
|
|
4808
|
+
},
|
|
4809
|
+
column.header
|
|
4810
|
+
);
|
|
4811
|
+
})
|
|
4724
4812
|
] }) }),
|
|
4725
4813
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
|
|
4726
4814
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
@@ -4778,13 +4866,18 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4778
4866
|
] })
|
|
4779
4867
|
] }),
|
|
4780
4868
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4781
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4869
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4870
|
+
import_design_system13.Button,
|
|
4871
|
+
{
|
|
4872
|
+
size: "sm",
|
|
4873
|
+
disabled: busy || !rights.write || rows.length === 0 || titleMissing,
|
|
4874
|
+
onClick: () => void run(),
|
|
4875
|
+
children: phase === "declaring" ? "Adding the columns\u2026" : phase === "writing" ? `Writing\u2026 ${progress} of ${rows.length}` : `Import ${rows.length} row${rows.length === 1 ? "" : "s"}`
|
|
4876
|
+
}
|
|
4877
|
+
),
|
|
4878
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: (0, import_design_system13.cn)("text-muted-foreground", titleMissing && "text-destructive"), children: [
|
|
4879
|
+
titleMissing ? `Nothing goes into ${titleFieldLabel} yet, so these records would have no name. Choose the column above, then this will run.` : `${mappedCount} of ${plan.columns.length} columns go into this table`,
|
|
4880
|
+
fileHash || titleMissing ? "" : " \xB7 this browser cannot recognise the file again, so importing it twice would write it twice"
|
|
4788
4881
|
] })
|
|
4789
4882
|
] })
|
|
4790
4883
|
] }) : null,
|
|
@@ -4802,27 +4895,31 @@ function ImportWizard({ tableId, onDone, className }) {
|
|
|
4802
4895
|
] }) : null
|
|
4803
4896
|
] }) : null,
|
|
4804
4897
|
alreadyRan ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: alreadyRan }) : null,
|
|
4805
|
-
outcomes.length > 0 || summary ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4898
|
+
outcomes.length > 0 || summary || ledger ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4806
4899
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_design_system13.Separator, {}),
|
|
4807
4900
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
4808
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
counts.duplicate,
|
|
4812
|
-
" already here \xB7 ",
|
|
4813
|
-
counts.refused,
|
|
4814
|
-
" refused"
|
|
4815
|
-
] }),
|
|
4816
|
-
counts.refused + counts.duplicate > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4901
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: ledger ? `${ledger.written} landed \xB7 ${ledger.duplicate} already here \xB7 ${ledger.refused} refused, of ${ledger.seen} row${ledger.seen === 1 ? "" : "s"} offered` : `${counts.landed} landed \xB7 ${counts.duplicate} already here \xB7 ${counts.refused} refused` }),
|
|
4902
|
+
ledger && !ledger.final && phase !== "writing" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-destructive", children: "The run stopped here, so the rest of the file was never offered \u2014 these are the rows that were dealt with before it stopped, not the whole file." }) : null,
|
|
4903
|
+
(ledger ? ledger.refused + ledger.duplicate : counts.refused + counts.duplicate) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
4817
4904
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: (0, import_design_system13.cn)("underline underline-offset-2", show === "all" && "font-medium"), onClick: () => setShow("all"), children: "everything that did not land" }),
|
|
4818
4905
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: (0, import_design_system13.cn)("underline underline-offset-2", show === "refused" && "font-medium"), onClick: () => setShow("refused"), children: "refused" }),
|
|
4819
4906
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: (0, import_design_system13.cn)("underline underline-offset-2", show === "duplicate" && "font-medium"), onClick: () => setShow("duplicate"), children: "already here" })
|
|
4820
4907
|
] }) : null
|
|
4821
4908
|
] }),
|
|
4822
4909
|
summary ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground", children: summary }) : null,
|
|
4910
|
+
ledger && show === "all" && ledger.refused + ledger.duplicate > interesting.length ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-muted-foreground", children: [
|
|
4911
|
+
interesting.length,
|
|
4912
|
+
" of the ",
|
|
4913
|
+
ledger.refused + ledger.duplicate,
|
|
4914
|
+
" row",
|
|
4915
|
+
ledger.refused + ledger.duplicate === 1 ? " that" : "s that",
|
|
4916
|
+
" did not land",
|
|
4917
|
+
interesting.length === 1 ? " is" : " are",
|
|
4918
|
+
" listed below by their line in your file \u2014 the store keeps the rest with this run and does not hand every one of them back at once."
|
|
4919
|
+
] }) : null,
|
|
4823
4920
|
interesting.length > 0 ? /* @__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: [
|
|
4824
4921
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("thead", { className: "sticky top-0 bg-muted", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("tr", { children: [
|
|
4825
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "
|
|
4922
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "Line in your file" }),
|
|
4826
4923
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "What happened" }),
|
|
4827
4924
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("th", { className: "px-2 py-1 text-left font-medium", children: "The row in your file" })
|
|
4828
4925
|
] }) }),
|
|
@@ -5035,6 +5132,9 @@ var ROLLUP_OPERATIONS = ROLLUP_AGG_VALUES.map((value) => ({
|
|
|
5035
5132
|
value,
|
|
5036
5133
|
label: ROLLUP_AGG_LABEL[value]
|
|
5037
5134
|
}));
|
|
5135
|
+
var FORMULA_SEPARATOR_LABEL = "Joined by";
|
|
5136
|
+
var EDITING_A_WORKED_OUT_COLUMN = "This column works out its answer from other columns. Which columns it uses, and what joins them, cannot be changed on this screen yet \u2014 remove it and add it again to change them.";
|
|
5137
|
+
var FORMULA_DEFAULT_SEPARATOR = " \u2014 ";
|
|
5038
5138
|
var FORMULA_OPERATIONS = FORMULA_OP_VALUES.map((value) => ({
|
|
5039
5139
|
value,
|
|
5040
5140
|
label: FORMULA_OP_LABEL[value],
|
|
@@ -5071,8 +5171,9 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5071
5171
|
const [of, setOf] = (0, import_react30.useState)(String(field?.config?.["of"] ?? ""));
|
|
5072
5172
|
const [relationTarget, setRelationTarget] = (0, import_react30.useState)(String(field?.relation_target ?? ""));
|
|
5073
5173
|
const [display, setDisplay] = (0, import_react30.useState)(field?.display ?? null);
|
|
5074
|
-
const [formulaOp, setFormulaOp] = (0, import_react30.useState)(
|
|
5075
|
-
const [formulaFields, setFormulaFields] = (0, import_react30.useState)(
|
|
5174
|
+
const [formulaOp, setFormulaOp] = (0, import_react30.useState)(() => formulaOpOf(field));
|
|
5175
|
+
const [formulaFields, setFormulaFields] = (0, import_react30.useState)(() => formulaFieldIdsOf(field));
|
|
5176
|
+
const [formulaSeparator, setFormulaSeparator] = (0, import_react30.useState)(() => formulaSeparatorOf(field));
|
|
5076
5177
|
const [askingToRemove, setAskingToRemove] = (0, import_react30.useState)(false);
|
|
5077
5178
|
const [notLanded, setNotLanded] = (0, import_react30.useState)(false);
|
|
5078
5179
|
const others = (0, import_react30.useMemo)(
|
|
@@ -5146,7 +5247,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5146
5247
|
...type === "datetime" ? { kind: withTime ? "datetime" : "date" } : {},
|
|
5147
5248
|
...type === "lookup" ? { via, pick } : {},
|
|
5148
5249
|
...type === "rollup" ? { via, agg, ...agg === "count" ? {} : { of } } : {},
|
|
5149
|
-
...type === "formula" ? { expr: formulaExpression(formulaOp, formulaFields) } : {}
|
|
5250
|
+
...type === "formula" ? { expr: formulaExpression(formulaOp, formulaFields, formulaSeparator) } : {}
|
|
5150
5251
|
};
|
|
5151
5252
|
const written = await mutation.declare({ table_id: tableId, spec });
|
|
5152
5253
|
if (written === null) return;
|
|
@@ -5204,6 +5305,7 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5204
5305
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: editing ? "What a column holds cannot be changed here \u2014 every value already saved would have to be converted. Add a new column and move the values across." : choice?.explanation ?? "" })
|
|
5205
5306
|
] })
|
|
5206
5307
|
] }),
|
|
5308
|
+
editing && type === "formula" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: EDITING_A_WORKED_OUT_COLUMN }) : null,
|
|
5207
5309
|
!editing || type === "select" || type === "multi_select" || type === "relation" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5208
5310
|
TypeQuestions,
|
|
5209
5311
|
{
|
|
@@ -5232,6 +5334,8 @@ function FieldEditor({ tableId, field, onSaved, onCancel, onRemoved, className }
|
|
|
5232
5334
|
setFormulaOp,
|
|
5233
5335
|
formulaFields,
|
|
5234
5336
|
setFormulaFields,
|
|
5337
|
+
formulaSeparator,
|
|
5338
|
+
setFormulaSeparator,
|
|
5235
5339
|
relations,
|
|
5236
5340
|
others,
|
|
5237
5341
|
existingOptionsTable: Boolean(field?.options_table_id ?? field?.config?.["options_table_id"])
|
|
@@ -5416,10 +5520,30 @@ function startingType(field) {
|
|
|
5416
5520
|
if (fieldTypeChoice(kind)) return kind;
|
|
5417
5521
|
return "text";
|
|
5418
5522
|
}
|
|
5419
|
-
function formulaExpression(op, fieldIds) {
|
|
5523
|
+
function formulaExpression(op, fieldIds, separator) {
|
|
5420
5524
|
const args = fieldIds.filter((id) => id !== "").map((id) => ({ field: id }));
|
|
5421
|
-
|
|
5422
|
-
return { op, args };
|
|
5525
|
+
const joined = op === "concat" && separator !== "" ? { separator } : {};
|
|
5526
|
+
if (args.length === 0) return { op, ...joined, args: [{ const: "" }] };
|
|
5527
|
+
return { op, ...joined, args };
|
|
5528
|
+
}
|
|
5529
|
+
function formulaExprOf(field) {
|
|
5530
|
+
const expr = field?.config?.["expr"];
|
|
5531
|
+
return expr !== null && typeof expr === "object" ? expr : null;
|
|
5532
|
+
}
|
|
5533
|
+
function formulaOpOf(field) {
|
|
5534
|
+
const op = formulaExprOf(field)?.["op"];
|
|
5535
|
+
return typeof op === "string" && op !== "" ? op : "concat";
|
|
5536
|
+
}
|
|
5537
|
+
function formulaFieldIdsOf(field) {
|
|
5538
|
+
const args = formulaExprOf(field)?.["args"];
|
|
5539
|
+
if (!Array.isArray(args)) return [];
|
|
5540
|
+
return args.map((a) => a !== null && typeof a === "object" ? a["field"] : void 0).filter((id) => typeof id === "string" && id !== "");
|
|
5541
|
+
}
|
|
5542
|
+
function formulaSeparatorOf(field) {
|
|
5543
|
+
const expr = formulaExprOf(field);
|
|
5544
|
+
if (expr === null) return FORMULA_DEFAULT_SEPARATOR;
|
|
5545
|
+
const sep = expr["separator"];
|
|
5546
|
+
return typeof sep === "string" ? sep : "";
|
|
5423
5547
|
}
|
|
5424
5548
|
function TypeQuestions(props) {
|
|
5425
5549
|
const { type } = props;
|
|
@@ -5558,7 +5682,21 @@ function TypeQuestions(props) {
|
|
|
5558
5682
|
);
|
|
5559
5683
|
}) }),
|
|
5560
5684
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "In the order you pick them. Columns are remembered by which column they are, so renaming one never breaks this." })
|
|
5561
|
-
] })
|
|
5685
|
+
] }),
|
|
5686
|
+
props.formulaOp === "concat" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
5687
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_design_system15.Label, { className: "text-xs", children: FORMULA_SEPARATOR_LABEL }),
|
|
5688
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
5689
|
+
import_design_system15.BasicInput,
|
|
5690
|
+
{
|
|
5691
|
+
value: props.formulaSeparator,
|
|
5692
|
+
"aria-label": FORMULA_SEPARATOR_LABEL,
|
|
5693
|
+
placeholder: "\u2014",
|
|
5694
|
+
className: "max-w-[10rem]",
|
|
5695
|
+
onChange: (e) => props.setFormulaSeparator(e.target.value)
|
|
5696
|
+
}
|
|
5697
|
+
),
|
|
5698
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "What goes between the columns. Leave it empty to run them straight together." })
|
|
5699
|
+
] }) : null
|
|
5562
5700
|
] });
|
|
5563
5701
|
}
|
|
5564
5702
|
return null;
|
|
@@ -6201,8 +6339,8 @@ function TableSettings({
|
|
|
6201
6339
|
const table = (0, import_react36.useTable)(tableId);
|
|
6202
6340
|
const fields = (0, import_react36.useFields)(tableId);
|
|
6203
6341
|
const rights = useTableRights(table.data);
|
|
6204
|
-
const mutation = (0, import_react36.useRecordMutation)();
|
|
6205
6342
|
const shape = (0, import_react36.useFieldMutation)();
|
|
6343
|
+
const archive = (0, import_react36.useTableArchive)(tableId);
|
|
6206
6344
|
const [editing, setEditing] = (0, import_react35.useState)(null);
|
|
6207
6345
|
const [askingToDelete, setAskingToDelete] = (0, import_react35.useState)(false);
|
|
6208
6346
|
const [askingToRemove, setAskingToRemove] = (0, import_react35.useState)(null);
|
|
@@ -6227,10 +6365,16 @@ function TableSettings({
|
|
|
6227
6365
|
table.reload();
|
|
6228
6366
|
}
|
|
6229
6367
|
};
|
|
6230
|
-
const
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6368
|
+
const startArchive = async () => {
|
|
6369
|
+
const finished = await archive.start();
|
|
6370
|
+
if (finished?.done && finished.table_archived) {
|
|
6371
|
+
setAskingToDelete(false);
|
|
6372
|
+
onDeleted?.();
|
|
6373
|
+
}
|
|
6374
|
+
};
|
|
6375
|
+
const askToArchive = async () => {
|
|
6376
|
+
setAskingToDelete(true);
|
|
6377
|
+
await archive.preview();
|
|
6234
6378
|
};
|
|
6235
6379
|
if (enriching) {
|
|
6236
6380
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, children: [
|
|
@@ -6266,7 +6410,6 @@ function TableSettings({
|
|
|
6266
6410
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: rows.length }),
|
|
6267
6411
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "outline", className: "ml-auto", onClick: () => setEditing("new"), children: "Add a field" })
|
|
6268
6412
|
] }),
|
|
6269
|
-
mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: mutation.error }) : null,
|
|
6270
6413
|
shape.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: shape.error }) : null,
|
|
6271
6414
|
askingToRemove ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap items-center gap-2 rounded border border-destructive/40 px-2 py-1.5", children: [
|
|
6272
6415
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-destructive", children: [
|
|
@@ -6357,21 +6500,32 @@ function TableSettings({
|
|
|
6357
6500
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "This table" }),
|
|
6358
6501
|
askingToDelete ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
6359
6502
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "text-muted-foreground", children: [
|
|
6360
|
-
"
|
|
6503
|
+
"Archiving ",
|
|
6361
6504
|
table.data ? tableName(table.data) : "this table",
|
|
6362
6505
|
" takes it out of everyone's list, with its ",
|
|
6363
6506
|
rows.length,
|
|
6364
6507
|
" field",
|
|
6365
6508
|
rows.length === 1 ? "" : "s",
|
|
6366
|
-
" and
|
|
6367
|
-
|
|
6509
|
+
" and",
|
|
6510
|
+
" ",
|
|
6511
|
+
archive.progress ? `its ${archive.progress.remaining.toLocaleString()} record${archive.progress.remaining === 1 ? "" : "s"}` : "every record in it",
|
|
6512
|
+
".",
|
|
6513
|
+
table.data?.retention_days ? ` The store keeps them for ${table.data.retention_days} days, so it can still be brought back.` : " The store keeps them, so it can still be brought back."
|
|
6368
6514
|
] }),
|
|
6369
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("
|
|
6370
|
-
|
|
6515
|
+
archive.progress && (archive.running || archive.passes > 0) ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: archive.error ? "text-destructive" : "text-muted-foreground", children: [
|
|
6516
|
+
archive.progress.total - archive.progress.remaining === archive.progress.total ? `All ${archive.progress.total.toLocaleString()} record${archive.progress.total === 1 ? "" : "s"} are put away.` : `${(archive.progress.total - archive.progress.remaining).toLocaleString()} of ${archive.progress.total.toLocaleString()} record${archive.progress.total === 1 ? "" : "s"} put away so far.`,
|
|
6517
|
+
archive.slowedDown ? " The store is busy, so it is taking smaller passes \u2014 it will still finish." : ""
|
|
6518
|
+
] }) : null,
|
|
6519
|
+
archive.interrupted && archive.progress ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-muted-foreground", children: archive.error ? `That pass did not finish, so the rest is still here. ${archive.progress.archived_total.toLocaleString()} record${archive.progress.archived_total === 1 ? " is" : "s are"} already archived and ${archive.progress.remaining.toLocaleString()} ${archive.progress.remaining === 1 ? "is" : "are"} left. Nothing was lost \u2014 carry on and it picks up where it stopped.` : `Stopped. ${archive.progress.archived_total.toLocaleString()} record${archive.progress.archived_total === 1 ? " is" : "s are"} archived and ${archive.progress.remaining.toLocaleString()} ${archive.progress.remaining === 1 ? "is" : "are"} left. Carry on whenever you like \u2014 it picks up where it stopped.` }) : null,
|
|
6520
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex gap-1", children: archive.running ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
6521
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "destructive", disabled: true, children: "Archiving\u2026" }),
|
|
6522
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => archive.stop(), children: "Stop" })
|
|
6523
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
6524
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "destructive", onClick: () => void startArchive(), children: archive.interrupted ? "Carry on archiving" : "Archive this table" }),
|
|
6371
6525
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => setAskingToDelete(false), children: "Keep it" })
|
|
6372
|
-
] })
|
|
6373
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "outline", onClick: () =>
|
|
6374
|
-
|
|
6526
|
+
] }) })
|
|
6527
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "outline", onClick: () => void askToArchive(), children: "Archive this table" }) }),
|
|
6528
|
+
archive.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: archive.error }) : null
|
|
6375
6529
|
] })
|
|
6376
6530
|
] });
|
|
6377
6531
|
}
|
|
@@ -7671,6 +7825,7 @@ function ViewSwitcher({
|
|
|
7671
7825
|
)
|
|
7672
7826
|
] });
|
|
7673
7827
|
}
|
|
7828
|
+
var UNUSUAL_FIELD_NOTE = "an unusual column to use here";
|
|
7674
7829
|
function offerableFields(all, want) {
|
|
7675
7830
|
return all.filter((field) => {
|
|
7676
7831
|
const kind = editorKindFor(field);
|
|
@@ -7806,10 +7961,7 @@ function FieldPicker({
|
|
|
7806
7961
|
children: [
|
|
7807
7962
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: "", children: "Choose a field" }),
|
|
7808
7963
|
offerable.map((field) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: field.key, children: fieldName(field) }, field.key)),
|
|
7809
|
-
unusual ? /* @__PURE__ */ (0, import_jsx_runtime27.
|
|
7810
|
-
fieldName(unusual),
|
|
7811
|
-
" \u2014 an unusual column to use here"
|
|
7812
|
-
] }, unusual.key) : null
|
|
7964
|
+
unusual ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: unusual.key, children: `${fieldName(unusual)} \u2014 ${UNUSUAL_FIELD_NOTE}` }, unusual.key) : null
|
|
7813
7965
|
]
|
|
7814
7966
|
}
|
|
7815
7967
|
),
|
|
@@ -13568,8 +13720,8 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13568
13720
|
[client, tableId, load, filter]
|
|
13569
13721
|
);
|
|
13570
13722
|
async function create() {
|
|
13571
|
-
const
|
|
13572
|
-
const byField =
|
|
13723
|
+
const all = fields.data ?? [];
|
|
13724
|
+
const byField = offerableFields(all, "choice")[0] ?? all.find((f) => f.type === "text" || f.type === "list");
|
|
13573
13725
|
const by = byField?.key;
|
|
13574
13726
|
await declare2(
|
|
13575
13727
|
{
|
|
@@ -13740,6 +13892,32 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13740
13892
|
children: CHART_KINDS.map((kind) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("option", { value: kind, children: CHART_KIND_LABEL[kind] }, kind))
|
|
13741
13893
|
}
|
|
13742
13894
|
),
|
|
13895
|
+
(board.blocks[index].groupBy ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13896
|
+
GroupingPicker,
|
|
13897
|
+
{
|
|
13898
|
+
block: board.blocks[index],
|
|
13899
|
+
fields: fields.data ?? [],
|
|
13900
|
+
onPick: (key) => void declare2(
|
|
13901
|
+
board,
|
|
13902
|
+
board.blocks.map((b, i) => {
|
|
13903
|
+
if (i !== index) return b;
|
|
13904
|
+
const picked = (fields.data ?? []).find((f) => f.key === key);
|
|
13905
|
+
const wasNamedAfterItsColumn = (fields.data ?? []).some(
|
|
13906
|
+
(f) => b.title === `By ${fieldName(f)}`
|
|
13907
|
+
);
|
|
13908
|
+
return {
|
|
13909
|
+
...b,
|
|
13910
|
+
groupBy: [key],
|
|
13911
|
+
// A block titled after the column it grouped by
|
|
13912
|
+
// must not keep saying "By Job Number" once it
|
|
13913
|
+
// groups by Status. A title somebody wrote
|
|
13914
|
+
// themselves is left alone.
|
|
13915
|
+
...wasNamedAfterItsColumn && picked ? { title: `By ${fieldName(picked)}` } : {}
|
|
13916
|
+
};
|
|
13917
|
+
})
|
|
13918
|
+
)
|
|
13919
|
+
}
|
|
13920
|
+
) : null,
|
|
13743
13921
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13744
13922
|
import_design_system47.Button,
|
|
13745
13923
|
{
|
|
@@ -13758,6 +13936,45 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13758
13936
|
] })
|
|
13759
13937
|
] });
|
|
13760
13938
|
}
|
|
13939
|
+
function groupingChoices(all) {
|
|
13940
|
+
const once = [];
|
|
13941
|
+
for (const field of all) if (!once.some((f) => f.key === field.key)) once.push(field);
|
|
13942
|
+
const sensible = offerableFields(once, "choice");
|
|
13943
|
+
const isSensible = (field) => sensible.some((s) => s.key === field.key);
|
|
13944
|
+
return [
|
|
13945
|
+
...sensible.map((field) => ({ field, unusual: false })),
|
|
13946
|
+
...once.filter((field) => !isSensible(field)).map((field) => ({ field, unusual: true }))
|
|
13947
|
+
];
|
|
13948
|
+
}
|
|
13949
|
+
function GroupingPicker({
|
|
13950
|
+
block,
|
|
13951
|
+
fields,
|
|
13952
|
+
onPick
|
|
13953
|
+
}) {
|
|
13954
|
+
const choices = groupingChoices(fields);
|
|
13955
|
+
const current = block.groupBy?.[0] ?? "";
|
|
13956
|
+
if (choices.length === 0) {
|
|
13957
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "text-muted-foreground", children: "This table has no columns yet, so there is nothing to group this block by." });
|
|
13958
|
+
}
|
|
13959
|
+
const known = choices.some((c) => c.field.key === current);
|
|
13960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
|
|
13961
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("label", { className: "text-muted-foreground", htmlFor: `block-group-${block.title}`, children: "Group by" }),
|
|
13962
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_design_system47.Select, { value: known ? current : "", onValueChange: onPick, children: [
|
|
13963
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13964
|
+
import_design_system47.SelectTrigger,
|
|
13965
|
+
{
|
|
13966
|
+
size: "sm",
|
|
13967
|
+
id: `block-group-${block.title}`,
|
|
13968
|
+
"aria-label": `Which column ${block.title} is grouped by`,
|
|
13969
|
+
className: "h-7 min-w-[10rem] text-xs",
|
|
13970
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_design_system47.SelectValue, { placeholder: "Choose a column" })
|
|
13971
|
+
}
|
|
13972
|
+
),
|
|
13973
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_design_system47.SelectContent, { children: choices.map(({ field, unusual }) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_design_system47.SelectItem, { value: field.key, children: unusual ? `${fieldName(field)} \u2014 ${UNUSUAL_FIELD_NOTE}` : fieldName(field) }, field.key)) })
|
|
13974
|
+
] }),
|
|
13975
|
+
known ? null : /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "text-muted-foreground", children: "The column this block was grouped by is no longer on this table \u2014 choose another." })
|
|
13976
|
+
] });
|
|
13977
|
+
}
|
|
13761
13978
|
|
|
13762
13979
|
// src/FormsPanel.tsx
|
|
13763
13980
|
var import_react98 = require("react");
|