@ai-matrx/records-ui 0.68.0 → 0.70.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 +178 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -44
- package/dist/index.d.ts +85 -44
- package/dist/index.js +179 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -667,22 +667,28 @@ import {
|
|
|
667
667
|
cn as cn2
|
|
668
668
|
} from "@ai-matrx/design-system";
|
|
669
669
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
670
|
-
function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
670
|
+
function RelationPicker({ field, value, onChange, disabled, id, className, allowCreate = true }) {
|
|
671
671
|
const client = useRecordsClient();
|
|
672
672
|
const [rows, setRows] = useState2(null);
|
|
673
673
|
const [words, setWords] = useState2({});
|
|
674
674
|
const [refusal, setRefusal] = useState2(null);
|
|
675
675
|
const [search, setSearch] = useState2("");
|
|
676
676
|
const [open, setOpen] = useState2(false);
|
|
677
|
+
const [target, setTarget] = useState2(null);
|
|
678
|
+
const [targetRefusal, setTargetRefusal] = useState2(null);
|
|
679
|
+
const [creating, setCreating] = useState2(false);
|
|
680
|
+
const [newTitle, setNewTitle] = useState2("");
|
|
681
|
+
const [busy, setBusy] = useState2(false);
|
|
682
|
+
const [createRefusal, setCreateRefusal] = useState2(null);
|
|
677
683
|
const picked = useMemo3(
|
|
678
684
|
() => Array.isArray(value) ? value.map(String) : value === null || value === void 0 ? [] : [String(value)],
|
|
679
685
|
[value]
|
|
680
686
|
);
|
|
681
687
|
useEffect(() => {
|
|
682
|
-
const
|
|
683
|
-
if (!
|
|
688
|
+
const target2 = field.relation_target;
|
|
689
|
+
if (!target2) return;
|
|
684
690
|
let cancelled = false;
|
|
685
|
-
void client.list({ table_id:
|
|
691
|
+
void client.list({ table_id: target2, limit: 200 }).then((result) => {
|
|
686
692
|
if (cancelled) return;
|
|
687
693
|
if (result.ok) setRows(result.data.rows);
|
|
688
694
|
else setRefusal(refusalLineForAPerson(result.error));
|
|
@@ -711,6 +717,28 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
711
717
|
cancelled = true;
|
|
712
718
|
};
|
|
713
719
|
}, [client, field.id, rows, picked, words]);
|
|
720
|
+
useEffect(() => {
|
|
721
|
+
if (!open || !allowCreate || target || targetRefusal) return;
|
|
722
|
+
const id2 = field.relation_target;
|
|
723
|
+
if (!id2) return;
|
|
724
|
+
let cancelled = false;
|
|
725
|
+
void client.tableList().then((result) => {
|
|
726
|
+
if (cancelled) return;
|
|
727
|
+
if (!result.ok) {
|
|
728
|
+
setTargetRefusal(refusalLineForAPerson(result.error));
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const found = result.data.find((t) => t.id === id2) ?? null;
|
|
732
|
+
if (found) setTarget(found);
|
|
733
|
+
else
|
|
734
|
+
setTargetRefusal(
|
|
735
|
+
"That Table is not one you can open, so a new record cannot be added to it from here."
|
|
736
|
+
);
|
|
737
|
+
});
|
|
738
|
+
return () => {
|
|
739
|
+
cancelled = true;
|
|
740
|
+
};
|
|
741
|
+
}, [client, open, allowCreate, target, targetRefusal, field.relation_target]);
|
|
714
742
|
if (!field.relation_target) {
|
|
715
743
|
return /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
|
|
716
744
|
fieldName(field),
|
|
@@ -730,6 +758,39 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
730
758
|
else if (!full) next.add(recordId);
|
|
731
759
|
onChange(next.size === 0 ? null : [...next]);
|
|
732
760
|
};
|
|
761
|
+
const targetWord = (target?.label_singular ?? "").trim() || (target?.name ?? "").trim() || "record";
|
|
762
|
+
const create = async (rawTitle) => {
|
|
763
|
+
const title = rawTitle.trim();
|
|
764
|
+
const tableId = field.relation_target;
|
|
765
|
+
if (!tableId || title === "" || busy) return;
|
|
766
|
+
if (!target) {
|
|
767
|
+
setCreateRefusal("Still reading that Table. Try again in a moment.");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const titleField = target.title_field;
|
|
771
|
+
if (!titleField) {
|
|
772
|
+
setCreateRefusal(
|
|
773
|
+
`${target.name} has no column that names a record yet, so a new one would have nothing to show here. A table admin picks its title column in that table's settings.`
|
|
774
|
+
);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
setBusy(true);
|
|
778
|
+
setCreateRefusal(null);
|
|
779
|
+
const written = await client.recordWrite({ table_id: tableId, data: { [titleField]: title } });
|
|
780
|
+
if (!written.ok) {
|
|
781
|
+
setBusy(false);
|
|
782
|
+
setCreateRefusal(refusalLineForAPerson(written.error));
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const createdId = String(written.data);
|
|
786
|
+
const refreshed = await client.list({ table_id: tableId, limit: 200 });
|
|
787
|
+
if (refreshed.ok) setRows(refreshed.data.rows);
|
|
788
|
+
setBusy(false);
|
|
789
|
+
setCreating(false);
|
|
790
|
+
setNewTitle("");
|
|
791
|
+
setSearch("");
|
|
792
|
+
toggle(createdId);
|
|
793
|
+
};
|
|
733
794
|
const visible = (rows ?? []).filter(
|
|
734
795
|
(row) => search.trim() === "" ? true : titleOf(row.id).toLowerCase().includes(search.trim().toLowerCase())
|
|
735
796
|
);
|
|
@@ -776,7 +837,57 @@ function RelationPicker({ field, value, onChange, disabled, id, className }) {
|
|
|
776
837
|
}
|
|
777
838
|
) }, row.id)) }),
|
|
778
839
|
rows && visible.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null
|
|
779
|
-
] })
|
|
840
|
+
] }),
|
|
841
|
+
allowCreate && !full ? /* @__PURE__ */ jsxs2("div", { className: "mt-1 border-t pt-1", children: [
|
|
842
|
+
targetRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: targetRefusal }) : !target ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 p-1", children: [
|
|
843
|
+
/* @__PURE__ */ jsx3(
|
|
844
|
+
BasicInput2,
|
|
845
|
+
{
|
|
846
|
+
autoFocus: true,
|
|
847
|
+
value: newTitle,
|
|
848
|
+
onChange: (e) => setNewTitle(e.target.value),
|
|
849
|
+
onKeyDown: (e) => {
|
|
850
|
+
if (e.key === "Enter") {
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
void create(newTitle);
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
placeholder: `New ${targetWord}`,
|
|
856
|
+
className: "h-7 flex-1 text-xs",
|
|
857
|
+
"aria-label": `Name the new ${targetWord}`
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
/* @__PURE__ */ jsx3(
|
|
861
|
+
Button2,
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
size: "sm",
|
|
865
|
+
variant: "default",
|
|
866
|
+
disabled: busy || newTitle.trim() === "",
|
|
867
|
+
onClick: () => void create(newTitle),
|
|
868
|
+
children: busy ? "Creating\u2026" : "Create"
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
872
|
+
Button2,
|
|
873
|
+
{
|
|
874
|
+
type: "button",
|
|
875
|
+
size: "sm",
|
|
876
|
+
variant: "ghost",
|
|
877
|
+
className: "w-full justify-start text-xs",
|
|
878
|
+
disabled: busy,
|
|
879
|
+
onClick: () => {
|
|
880
|
+
if (search.trim() !== "") void create(search);
|
|
881
|
+
else {
|
|
882
|
+
setNewTitle("");
|
|
883
|
+
setCreating(true);
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
children: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`
|
|
887
|
+
}
|
|
888
|
+
),
|
|
889
|
+
createRefusal ? /* @__PURE__ */ jsx3("p", { className: "p-2 text-xs text-destructive", children: createRefusal }) : null
|
|
890
|
+
] }) : null
|
|
780
891
|
] })
|
|
781
892
|
] })
|
|
782
893
|
] });
|
|
@@ -3221,6 +3332,7 @@ var NO_COLUMNS_WHY = "A column is what a record holds \u2014 a name, a date, an
|
|
|
3221
3332
|
function Grid({
|
|
3222
3333
|
tableId,
|
|
3223
3334
|
pageSize = 50,
|
|
3335
|
+
filter,
|
|
3224
3336
|
onOpenRecord,
|
|
3225
3337
|
onAddField,
|
|
3226
3338
|
onNewRecordForm,
|
|
@@ -3236,7 +3348,7 @@ function Grid({
|
|
|
3236
3348
|
const table = useTable2(tableId);
|
|
3237
3349
|
const fields = useFields3(tableId);
|
|
3238
3350
|
const [page, setPage] = useState10(0);
|
|
3239
|
-
const records = useRecords2(tableId, { pageSize, page });
|
|
3351
|
+
const records = useRecords2(tableId, { pageSize, page, ...filter ? { filter } : {} });
|
|
3240
3352
|
const rights = useTableRights(table.data);
|
|
3241
3353
|
const rows = useMemo9(() => records.data?.rows ?? [], [records.data]);
|
|
3242
3354
|
const rowIds = useMemo9(() => rows.map((row) => row.id), [rows]);
|
|
@@ -5210,7 +5322,8 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
5210
5322
|
value: value === "" ? null : value,
|
|
5211
5323
|
onChange: (next) => write({ value: next === null || next === void 0 ? "" : String(next) }),
|
|
5212
5324
|
id: "condition-value-record",
|
|
5213
|
-
className: "min-w-[8rem]"
|
|
5325
|
+
className: "min-w-[8rem]",
|
|
5326
|
+
allowCreate: false
|
|
5214
5327
|
}
|
|
5215
5328
|
)
|
|
5216
5329
|
) : /* @__PURE__ */ jsx18(
|
|
@@ -7019,9 +7132,12 @@ function unplacedName(record, field, labels) {
|
|
|
7019
7132
|
// src/ViewSwitcher.tsx
|
|
7020
7133
|
import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
7021
7134
|
var NO_FIELDS = [];
|
|
7022
|
-
function useViewRecords(view, pageSize = 200) {
|
|
7135
|
+
function useViewRecords(view, pageSize = 200, filter) {
|
|
7023
7136
|
const client = useRecordsClient16();
|
|
7024
|
-
const table = useRecords5(view.ruleId ? null : view.subject, {
|
|
7137
|
+
const table = useRecords5(view.ruleId ? null : view.subject, {
|
|
7138
|
+
pageSize,
|
|
7139
|
+
...filter ? { filter } : {}
|
|
7140
|
+
});
|
|
7025
7141
|
const [ruled, setRuled] = useState24({
|
|
7026
7142
|
rows: [],
|
|
7027
7143
|
loading: Boolean(view.ruleId),
|
|
@@ -7076,6 +7192,7 @@ function ViewSwitcher({
|
|
|
7076
7192
|
onNewRecordForm,
|
|
7077
7193
|
onAskWhoChanged,
|
|
7078
7194
|
pageSize = 200,
|
|
7195
|
+
filter,
|
|
7079
7196
|
className
|
|
7080
7197
|
}) {
|
|
7081
7198
|
const [layout, setLayout] = useState24(view.layout);
|
|
@@ -7113,6 +7230,7 @@ function ViewSwitcher({
|
|
|
7113
7230
|
{
|
|
7114
7231
|
tableId: view.subject,
|
|
7115
7232
|
pageSize,
|
|
7233
|
+
...filter ? { filter } : {},
|
|
7116
7234
|
...local.presentation ?? view.presentation ? { presentation: local.presentation ?? view.presentation } : {},
|
|
7117
7235
|
...onViewChange ? { onPresentationChange: (next) => change({ presentation: next }) } : {},
|
|
7118
7236
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
@@ -7124,6 +7242,7 @@ function ViewSwitcher({
|
|
|
7124
7242
|
{
|
|
7125
7243
|
view: { ...view, ...local, layout },
|
|
7126
7244
|
pageSize,
|
|
7245
|
+
...filter ? { filter } : {},
|
|
7127
7246
|
onChange: change,
|
|
7128
7247
|
remembered: Boolean(onViewChange),
|
|
7129
7248
|
...onOpenRecord ? { onOpenRecord } : {},
|
|
@@ -7171,13 +7290,14 @@ function boardColumnChoice(fields, chosenKey, stage) {
|
|
|
7171
7290
|
function Board({
|
|
7172
7291
|
view,
|
|
7173
7292
|
pageSize,
|
|
7293
|
+
filter,
|
|
7174
7294
|
onChange,
|
|
7175
7295
|
remembered,
|
|
7176
7296
|
onOpenRecord,
|
|
7177
7297
|
onMoved
|
|
7178
7298
|
}) {
|
|
7179
7299
|
const fields = useFields12(view.subject);
|
|
7180
|
-
const records = useViewRecords(view, pageSize);
|
|
7300
|
+
const records = useViewRecords(view, pageSize, filter);
|
|
7181
7301
|
const stage = useStageField(view.subject);
|
|
7182
7302
|
if (fields.error) return /* @__PURE__ */ jsx26(RefusalNotice, { error: fields.error });
|
|
7183
7303
|
if (records.error) return /* @__PURE__ */ jsx26(RefusalNotice, { error: records.error });
|
|
@@ -12740,14 +12860,20 @@ function DashboardCanvas({ tableId, activeDashboardId, filter, className }) {
|
|
|
12740
12860
|
rights.admin ? /* @__PURE__ */ jsx47(Button39, { size: "sm", variant: "ghost", onClick: () => void remove(board), children: "Delete" }) : null
|
|
12741
12861
|
] }) : null
|
|
12742
12862
|
] }),
|
|
12743
|
-
board && scheduling ?
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
|
|
12863
|
+
board && scheduling ? (
|
|
12864
|
+
// ONE SHAPE, SO NO CAST. The dashboard's own filter, the saved view it
|
|
12865
|
+
// schedules over and the drill-through it opens are all the same
|
|
12866
|
+
// `RecordFilter`; the `as Record<string, unknown>` that used to be here
|
|
12867
|
+
// was the seam where they could have drifted.
|
|
12868
|
+
/* @__PURE__ */ jsx47(
|
|
12869
|
+
DigestScheduler,
|
|
12870
|
+
{
|
|
12871
|
+
tableId,
|
|
12872
|
+
subjectName: board.name,
|
|
12873
|
+
...filter ? { filters: filter } : {},
|
|
12874
|
+
onClose: () => setScheduling(false)
|
|
12875
|
+
}
|
|
12876
|
+
)
|
|
12751
12877
|
) : null,
|
|
12752
12878
|
board ? /* @__PURE__ */ jsxs43("div", { className: "flex flex-col gap-1", children: [
|
|
12753
12879
|
/* @__PURE__ */ jsxs43("div", { className: "flex items-center gap-1", children: [
|
|
@@ -14750,12 +14876,38 @@ function TablesHome({ onOpenTable, className }) {
|
|
|
14750
14876
|
|
|
14751
14877
|
// src/TablePage.tsx
|
|
14752
14878
|
import { useCallback as useCallback37, useEffect as useEffect41, useState as useState53 } from "react";
|
|
14753
|
-
import { useRecordsClient as useRecordsClient43, useTable as useTable22 } from "@ai-matrx/records/react";
|
|
14879
|
+
import { useFields as useFields23, useRecordsClient as useRecordsClient43, useTable as useTable22 } from "@ai-matrx/records/react";
|
|
14754
14880
|
import { Button as Button48, Separator as Separator12, Skeleton as Skeleton34, cn as cn51 } from "@ai-matrx/design-system";
|
|
14755
14881
|
import { Fragment as Fragment28, jsx as jsx58, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
14756
|
-
function
|
|
14757
|
-
const
|
|
14758
|
-
|
|
14882
|
+
function filterInWords(filter, fields) {
|
|
14883
|
+
const nameOf = (key) => {
|
|
14884
|
+
const field = fields.find((f) => f.key === key);
|
|
14885
|
+
return field ? fieldName(field) : humanize(key);
|
|
14886
|
+
};
|
|
14887
|
+
const parts = Object.keys(filter).map((key) => {
|
|
14888
|
+
const value = filter[key];
|
|
14889
|
+
if (value === null || value === void 0) return `${nameOf(key)} is not filled in`;
|
|
14890
|
+
if (typeof value === "object") {
|
|
14891
|
+
const from = (value.from ?? "").toString().trim();
|
|
14892
|
+
const to = (value.to ?? "").toString().trim();
|
|
14893
|
+
if (from !== "" && to !== "") return `${nameOf(key)} from ${from} up to ${to}`;
|
|
14894
|
+
if (from !== "") return `${nameOf(key)} from ${from} onwards`;
|
|
14895
|
+
if (to !== "") return `${nameOf(key)} up to ${to}`;
|
|
14896
|
+
return `${nameOf(key)} in a period nobody named`;
|
|
14897
|
+
}
|
|
14898
|
+
return `${nameOf(key)} is ${String(value)}`;
|
|
14899
|
+
});
|
|
14900
|
+
if (parts.length === 0) return "";
|
|
14901
|
+
if (parts.length === 1) return parts[0];
|
|
14902
|
+
return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
14903
|
+
}
|
|
14904
|
+
function cameFromLine(label, groupField, question) {
|
|
14905
|
+
const asked = (question ?? "").trim();
|
|
14906
|
+
if (asked === "") {
|
|
14907
|
+
const grouped = groupField ? " grouped into the same columns the chart used" : "";
|
|
14908
|
+
return `You came here from \u201C${label}\u201D. This is every record in this table${grouped} \u2014 that number was counted over all of them.`;
|
|
14909
|
+
}
|
|
14910
|
+
return `You came here from \u201C${label}\u201D. These are only the records that number counted: ${asked}. Everything else in this table is being held back \u2014 clear the filter to see all of it.`;
|
|
14759
14911
|
}
|
|
14760
14912
|
var TABLE_NOT_REACHABLE = "This table is not in the organization you are working in, so there is nothing here to show. Switch to the organization that owns it and open it again \u2014 or it may have been deleted.";
|
|
14761
14913
|
var DEFAULT_VIEW_NAME = "All records";
|
|
@@ -14802,11 +14954,14 @@ function TablePage({
|
|
|
14802
14954
|
onViewChanged,
|
|
14803
14955
|
activeGroupField,
|
|
14804
14956
|
cameFrom,
|
|
14957
|
+
filter,
|
|
14805
14958
|
className
|
|
14806
14959
|
}) {
|
|
14807
14960
|
const client = useRecordsClient43();
|
|
14808
14961
|
const table = useTable22(tableId);
|
|
14809
14962
|
const rights = useTableRights(table.data);
|
|
14963
|
+
const pageFields = useFields23(filter ? tableId : null);
|
|
14964
|
+
const askedInWords = filter ? filterInWords(filter, pageFields.data ?? []) : null;
|
|
14810
14965
|
const organizationId = useRecordsClient43().config.organizationId;
|
|
14811
14966
|
const [view, setView] = useState53(null);
|
|
14812
14967
|
const opening = openingRail(activeRecordId);
|
|
@@ -15000,7 +15155,7 @@ function TablePage({
|
|
|
15000
15155
|
{
|
|
15001
15156
|
"data-testid": "came-from-a-number",
|
|
15002
15157
|
className: "rounded-md border px-3 py-2 text-xs leading-relaxed text-muted-foreground",
|
|
15003
|
-
children: cameFromLine(cameFrom, activeGroupField ?? null)
|
|
15158
|
+
children: cameFromLine(cameFrom, activeGroupField ?? null, askedInWords)
|
|
15004
15159
|
}
|
|
15005
15160
|
) : null,
|
|
15006
15161
|
/* @__PURE__ */ jsx58(
|
|
@@ -15014,6 +15169,7 @@ function TablePage({
|
|
|
15014
15169
|
...activeGroupField ? { groupField: activeGroupField } : {}
|
|
15015
15170
|
},
|
|
15016
15171
|
pageSize,
|
|
15172
|
+
...filter ? { filter } : {},
|
|
15017
15173
|
onLayoutChange: (layout) => {
|
|
15018
15174
|
setLayoutFromLink(layout);
|
|
15019
15175
|
onViewChanged?.(layout);
|