@ai-matrx/records-ui 0.77.0 → 0.78.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 +25 -0
- package/dist/index.cjs +263 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +319 -123
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records-ui
|
|
2
2
|
|
|
3
|
+
## 0.78.0
|
|
4
|
+
|
|
5
|
+
**The button that could not get rid of a table now can, and says what it does.** (lane
|
|
6
|
+
FIX-11A, VERIFIER-11 finding F3.)
|
|
7
|
+
|
|
8
|
+
`TableSettings` called `useRecordMutation().remove` with the TABLE's id — one unbounded
|
|
9
|
+
statement — so on a 1,430-record table it died twelve seconds in on `57014` and told the
|
|
10
|
+
person nothing had changed and to "try a smaller page". There is no page.
|
|
11
|
+
|
|
12
|
+
* The panel now drives `useTableArchive` from `@ai-matrx/records` 0.53.0: it previews
|
|
13
|
+
first (`chunk: 0`) so the confirm names the real number of records, then archives in
|
|
14
|
+
passes and shows "N of M records put away so far" while it works.
|
|
15
|
+
* **It never claims nothing happened.** A refused pass or a stopped run says what went
|
|
16
|
+
wrong, how many records are already archived and how many are left, and offers **Carry on
|
|
17
|
+
archiving**, which picks up exactly where it stopped. There is a **Stop** while it runs.
|
|
18
|
+
* When the store is busy the loop takes smaller passes, and the screen SAYS so rather than
|
|
19
|
+
going quietly four times slower.
|
|
20
|
+
* **The control's word matches what it does.** It said "Delete this table" over a sentence
|
|
21
|
+
explaining the store keeps everything for a year; the store's own verb is archive
|
|
22
|
+
(`table_archive`, `record_restore`, `read_records_archived`), so that is the word on the
|
|
23
|
+
button and the sentence says plainly it can be brought back.
|
|
24
|
+
|
|
25
|
+
Consumer action: none — the panel's props are unchanged; `onDeleted` still fires, now when
|
|
26
|
+
the Table itself has been archived.
|
|
27
|
+
|
|
3
28
|
## 0.77.0
|
|
4
29
|
|
|
5
30
|
**A publish button that would hand out a dead link is not drawn.** (lane STORE-OFF, from
|
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
|
] }) }),
|
|
@@ -6201,8 +6298,8 @@ function TableSettings({
|
|
|
6201
6298
|
const table = (0, import_react36.useTable)(tableId);
|
|
6202
6299
|
const fields = (0, import_react36.useFields)(tableId);
|
|
6203
6300
|
const rights = useTableRights(table.data);
|
|
6204
|
-
const mutation = (0, import_react36.useRecordMutation)();
|
|
6205
6301
|
const shape = (0, import_react36.useFieldMutation)();
|
|
6302
|
+
const archive = (0, import_react36.useTableArchive)(tableId);
|
|
6206
6303
|
const [editing, setEditing] = (0, import_react35.useState)(null);
|
|
6207
6304
|
const [askingToDelete, setAskingToDelete] = (0, import_react35.useState)(false);
|
|
6208
6305
|
const [askingToRemove, setAskingToRemove] = (0, import_react35.useState)(null);
|
|
@@ -6227,10 +6324,16 @@ function TableSettings({
|
|
|
6227
6324
|
table.reload();
|
|
6228
6325
|
}
|
|
6229
6326
|
};
|
|
6230
|
-
const
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6327
|
+
const startArchive = async () => {
|
|
6328
|
+
const finished = await archive.start();
|
|
6329
|
+
if (finished?.done && finished.table_archived) {
|
|
6330
|
+
setAskingToDelete(false);
|
|
6331
|
+
onDeleted?.();
|
|
6332
|
+
}
|
|
6333
|
+
};
|
|
6334
|
+
const askToArchive = async () => {
|
|
6335
|
+
setAskingToDelete(true);
|
|
6336
|
+
await archive.preview();
|
|
6234
6337
|
};
|
|
6235
6338
|
if (enriching) {
|
|
6236
6339
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, children: [
|
|
@@ -6266,7 +6369,6 @@ function TableSettings({
|
|
|
6266
6369
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-muted-foreground", children: rows.length }),
|
|
6267
6370
|
/* @__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
6371
|
] }),
|
|
6269
|
-
mutation.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: mutation.error }) : null,
|
|
6270
6372
|
shape.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: shape.error }) : null,
|
|
6271
6373
|
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
6374
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { className: "text-destructive", children: [
|
|
@@ -6357,21 +6459,32 @@ function TableSettings({
|
|
|
6357
6459
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "font-medium", children: "This table" }),
|
|
6358
6460
|
askingToDelete ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
6359
6461
|
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: "text-muted-foreground", children: [
|
|
6360
|
-
"
|
|
6462
|
+
"Archiving ",
|
|
6361
6463
|
table.data ? tableName(table.data) : "this table",
|
|
6362
6464
|
" takes it out of everyone's list, with its ",
|
|
6363
6465
|
rows.length,
|
|
6364
6466
|
" field",
|
|
6365
6467
|
rows.length === 1 ? "" : "s",
|
|
6366
|
-
" and
|
|
6367
|
-
|
|
6468
|
+
" and",
|
|
6469
|
+
" ",
|
|
6470
|
+
archive.progress ? `its ${archive.progress.remaining.toLocaleString()} record${archive.progress.remaining === 1 ? "" : "s"}` : "every record in it",
|
|
6471
|
+
".",
|
|
6472
|
+
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
6473
|
] }),
|
|
6369
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("
|
|
6370
|
-
|
|
6474
|
+
archive.progress && (archive.running || archive.passes > 0) ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { className: archive.error ? "text-destructive" : "text-muted-foreground", children: [
|
|
6475
|
+
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.`,
|
|
6476
|
+
archive.slowedDown ? " The store is busy, so it is taking smaller passes \u2014 it will still finish." : ""
|
|
6477
|
+
] }) : null,
|
|
6478
|
+
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,
|
|
6479
|
+
/* @__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: [
|
|
6480
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "destructive", disabled: true, children: "Archiving\u2026" }),
|
|
6481
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_design_system18.Button, { size: "sm", variant: "ghost", onClick: () => archive.stop(), children: "Stop" })
|
|
6482
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
|
|
6483
|
+
/* @__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
6484
|
/* @__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
|
-
|
|
6485
|
+
] }) })
|
|
6486
|
+
] }) : /* @__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" }) }),
|
|
6487
|
+
archive.error ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(RefusalNotice, { error: archive.error }) : null
|
|
6375
6488
|
] })
|
|
6376
6489
|
] });
|
|
6377
6490
|
}
|
|
@@ -7671,6 +7784,7 @@ function ViewSwitcher({
|
|
|
7671
7784
|
)
|
|
7672
7785
|
] });
|
|
7673
7786
|
}
|
|
7787
|
+
var UNUSUAL_FIELD_NOTE = "an unusual column to use here";
|
|
7674
7788
|
function offerableFields(all, want) {
|
|
7675
7789
|
return all.filter((field) => {
|
|
7676
7790
|
const kind = editorKindFor(field);
|
|
@@ -7806,10 +7920,7 @@ function FieldPicker({
|
|
|
7806
7920
|
children: [
|
|
7807
7921
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: "", children: "Choose a field" }),
|
|
7808
7922
|
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
|
|
7923
|
+
unusual ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("option", { value: unusual.key, children: `${fieldName(unusual)} \u2014 ${UNUSUAL_FIELD_NOTE}` }, unusual.key) : null
|
|
7813
7924
|
]
|
|
7814
7925
|
}
|
|
7815
7926
|
),
|
|
@@ -13568,8 +13679,8 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13568
13679
|
[client, tableId, load, filter]
|
|
13569
13680
|
);
|
|
13570
13681
|
async function create() {
|
|
13571
|
-
const
|
|
13572
|
-
const byField =
|
|
13682
|
+
const all = fields.data ?? [];
|
|
13683
|
+
const byField = offerableFields(all, "choice")[0] ?? all.find((f) => f.type === "text" || f.type === "list");
|
|
13573
13684
|
const by = byField?.key;
|
|
13574
13685
|
await declare2(
|
|
13575
13686
|
{
|
|
@@ -13740,6 +13851,32 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13740
13851
|
children: CHART_KINDS.map((kind) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("option", { value: kind, children: CHART_KIND_LABEL[kind] }, kind))
|
|
13741
13852
|
}
|
|
13742
13853
|
),
|
|
13854
|
+
(board.blocks[index].groupBy ?? []).length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13855
|
+
GroupingPicker,
|
|
13856
|
+
{
|
|
13857
|
+
block: board.blocks[index],
|
|
13858
|
+
fields: fields.data ?? [],
|
|
13859
|
+
onPick: (key) => void declare2(
|
|
13860
|
+
board,
|
|
13861
|
+
board.blocks.map((b, i) => {
|
|
13862
|
+
if (i !== index) return b;
|
|
13863
|
+
const picked = (fields.data ?? []).find((f) => f.key === key);
|
|
13864
|
+
const wasNamedAfterItsColumn = (fields.data ?? []).some(
|
|
13865
|
+
(f) => b.title === `By ${fieldName(f)}`
|
|
13866
|
+
);
|
|
13867
|
+
return {
|
|
13868
|
+
...b,
|
|
13869
|
+
groupBy: [key],
|
|
13870
|
+
// A block titled after the column it grouped by
|
|
13871
|
+
// must not keep saying "By Job Number" once it
|
|
13872
|
+
// groups by Status. A title somebody wrote
|
|
13873
|
+
// themselves is left alone.
|
|
13874
|
+
...wasNamedAfterItsColumn && picked ? { title: `By ${fieldName(picked)}` } : {}
|
|
13875
|
+
};
|
|
13876
|
+
})
|
|
13877
|
+
)
|
|
13878
|
+
}
|
|
13879
|
+
) : null,
|
|
13743
13880
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13744
13881
|
import_design_system47.Button,
|
|
13745
13882
|
{
|
|
@@ -13758,6 +13895,45 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
13758
13895
|
] })
|
|
13759
13896
|
] });
|
|
13760
13897
|
}
|
|
13898
|
+
function groupingChoices(all) {
|
|
13899
|
+
const once = [];
|
|
13900
|
+
for (const field of all) if (!once.some((f) => f.key === field.key)) once.push(field);
|
|
13901
|
+
const sensible = offerableFields(once, "choice");
|
|
13902
|
+
const isSensible = (field) => sensible.some((s) => s.key === field.key);
|
|
13903
|
+
return [
|
|
13904
|
+
...sensible.map((field) => ({ field, unusual: false })),
|
|
13905
|
+
...once.filter((field) => !isSensible(field)).map((field) => ({ field, unusual: true }))
|
|
13906
|
+
];
|
|
13907
|
+
}
|
|
13908
|
+
function GroupingPicker({
|
|
13909
|
+
block,
|
|
13910
|
+
fields,
|
|
13911
|
+
onPick
|
|
13912
|
+
}) {
|
|
13913
|
+
const choices = groupingChoices(fields);
|
|
13914
|
+
const current = block.groupBy?.[0] ?? "";
|
|
13915
|
+
if (choices.length === 0) {
|
|
13916
|
+
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." });
|
|
13917
|
+
}
|
|
13918
|
+
const known = choices.some((c) => c.field.key === current);
|
|
13919
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
|
|
13920
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("label", { className: "text-muted-foreground", htmlFor: `block-group-${block.title}`, children: "Group by" }),
|
|
13921
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_design_system47.Select, { value: known ? current : "", onValueChange: onPick, children: [
|
|
13922
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
13923
|
+
import_design_system47.SelectTrigger,
|
|
13924
|
+
{
|
|
13925
|
+
size: "sm",
|
|
13926
|
+
id: `block-group-${block.title}`,
|
|
13927
|
+
"aria-label": `Which column ${block.title} is grouped by`,
|
|
13928
|
+
className: "h-7 min-w-[10rem] text-xs",
|
|
13929
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(import_design_system47.SelectValue, { placeholder: "Choose a column" })
|
|
13930
|
+
}
|
|
13931
|
+
),
|
|
13932
|
+
/* @__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)) })
|
|
13933
|
+
] }),
|
|
13934
|
+
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." })
|
|
13935
|
+
] });
|
|
13936
|
+
}
|
|
13761
13937
|
|
|
13762
13938
|
// src/FormsPanel.tsx
|
|
13763
13939
|
var import_react98 = require("react");
|