@protobase/ui 0.23.10 → 0.23.12
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/dist/app/create/create-page.js +3 -2
- package/dist/app/create/create-record-form.js +4 -3
- package/dist/app/create/create-route.js +5 -8
- package/dist/app/create/create-values.js +2 -1
- package/dist/app/create/form.js +3 -2
- package/dist/app/create/messages.js +32 -0
- package/dist/app/delete/blocked-delete.js +6 -5
- package/dist/app/delete/dialogs.js +21 -20
- package/dist/app/delete/messages.js +66 -0
- package/dist/app/delete/use-delete-flow.js +7 -6
- package/dist/app/displays/display-controls.js +5 -4
- package/dist/app/displays/list-displays.js +2 -1
- package/dist/app/displays/messages.js +20 -0
- package/dist/app/embedded-list/cut-off-note.js +2 -1
- package/dist/app/embedded-list/embedded-list.js +11 -6
- package/dist/app/embedded-list/embedded-row-targets.js +4 -3
- package/dist/app/embedded-list/messages.js +24 -0
- package/dist/app/error-banner.js +2 -1
- package/dist/app/field-errors.js +19 -19
- package/dist/app/field-messages.js +48 -0
- package/dist/app/hand-order/hand-sorted-list.js +6 -11
- package/dist/app/hand-order/messages.js +22 -0
- package/dist/app/hand-order/sort-button.js +2 -1
- package/dist/app/hand-order/use-hand-order.js +2 -1
- package/dist/app/incoming-card.js +2 -1
- package/dist/app/list-edit/messages.js +14 -0
- package/dist/app/list-edit/use-list-edits.js +2 -1
- package/dist/app/list-empty-state.js +5 -5
- package/dist/app/list-empty.js +3 -2
- package/dist/app/list-messages.js +54 -0
- package/dist/app/list-page.js +24 -38
- package/dist/app/list-search.js +8 -7
- package/dist/app/messages.js +16 -0
- package/dist/app/pages/record-actions.js +4 -3
- package/dist/app/record-lines.js +3 -2
- package/dist/app/record-messages.js +78 -0
- package/dist/app/record-page.js +31 -32
- package/dist/app/related-section.js +5 -4
- package/dist/app/use-live-save.js +4 -3
- package/dist/data-grid/cell-text.js +2 -1
- package/dist/data-grid/cells.js +15 -14
- package/dist/data-grid/grid-footer.js +3 -2
- package/dist/data-grid/grid-header.js +4 -3
- package/dist/data-grid/grid-row.js +2 -1
- package/dist/data-grid/header-menu.js +5 -4
- package/dist/data-grid/messages.js +50 -0
- package/dist/data-grid/row-menu.js +2 -1
- package/dist/data-grid/row-reorder.js +8 -8
- package/dist/filter-panel/active-filter-chips.js +3 -2
- package/dist/filter-panel/date-presets.js +5 -4
- package/dist/filter-panel/facet-combobox.js +3 -2
- package/dist/filter-panel/facet-group.js +3 -2
- package/dist/filter-panel/filter-config.js +15 -12
- package/dist/filter-panel/filter-panel.js +4 -3
- package/dist/filter-panel/filter-state.js +2 -2
- package/dist/filter-panel/messages.js +52 -0
- package/dist/filter-panel/more-filters-menu.js +3 -2
- package/dist/filter-panel/range-filter.js +2 -1
- package/dist/filter-panel/secondary-filters.js +2 -1
- package/dist/forms/messages.js +24 -0
- package/dist/forms/prove-human.js +2 -1
- package/dist/forms/public-form-page.js +14 -8
- package/dist/live/display-value.js +3 -2
- package/dist/live/file-values.js +2 -1
- package/dist/live/messages.js +18 -0
- package/dist/package.js +1 -1
- package/dist/record-view/action-menu.js +2 -1
- package/dist/record-view/action-rail.js +2 -1
- package/dist/record-view/field.js +12 -7
- package/dist/record-view/messages.js +38 -0
- package/dist/record-view/record-header.js +8 -10
- package/dist/shell/assets/app-BRdywuCD.js +69 -0
- package/dist/shell/assets/export-worker-Bz57Pf5g.js +2 -0
- package/dist/shell/assets/{index-CPnp4jVD.js → index-HdKQ9gvF.js} +1 -1
- package/dist/shell/assets/{maplibre-CJlfYYmB.js → maplibre-BvFmZgXT.js} +1 -1
- package/dist/shell/assets/{markdown-content-BDgnIKSJ.js → markdown-content-BL8dxxr1.js} +1 -1
- package/dist/shell/assets/{record-map-BCWeSTdZ.js → record-map-DagdaaJK.js} +1 -1
- package/dist/shell/assets/{records-map-BhNGzm42.js → records-map-eM1ob8eA.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-C6_QptrK.js → shared-protobase-ui-D8qQezaz.js} +1 -1
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/package.json +4 -4
- package/dist/shell/assets/app-BPh2QhzJ.js +0 -69
- package/dist/shell/assets/export-worker-O1HF9R4j.js +0 -2
package/dist/app/record-lines.js
CHANGED
|
@@ -2,6 +2,7 @@ import { InlineChildTable } from "../inline-child-table/inline-child-table.js";
|
|
|
2
2
|
import { Section } from "../record-view/section.js";
|
|
3
3
|
import { usePermissions } from "./meta-gate.js";
|
|
4
4
|
import { can } from "./permissions.js";
|
|
5
|
+
import { text } from "./record-messages.js";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
//#region src/app/record-lines.tsx
|
|
7
8
|
/** A resource that holds numbered lines of this one: it points at the parent and has `position` and `quantity`. */
|
|
@@ -17,8 +18,8 @@ const toLine = (record) => ({
|
|
|
17
18
|
const RecordLines = ({ lines, draft, vatRate, saving, locked = false }) => {
|
|
18
19
|
const canReorder = can(usePermissions(lines.name), "update");
|
|
19
20
|
return /* @__PURE__ */ jsx(Section, {
|
|
20
|
-
title: "
|
|
21
|
-
help: "
|
|
21
|
+
title: text("lines"),
|
|
22
|
+
help: text("linesHelp"),
|
|
22
23
|
children: /* @__PURE__ */ jsx("div", {
|
|
23
24
|
className: "-mx-4 overflow-x-auto px-4 [-webkit-overflow-scrolling:touch] md:mx-0 md:px-0",
|
|
24
25
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/app/record-messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
details: "Details",
|
|
6
|
+
history: "History",
|
|
7
|
+
comments: "Comments",
|
|
8
|
+
summary: "Summary",
|
|
9
|
+
loading: "Loading",
|
|
10
|
+
fetching: "Fetching the record.",
|
|
11
|
+
noRecord: "This record does not exist, or you cannot see it.",
|
|
12
|
+
couldNotLoad: "Could not load the record",
|
|
13
|
+
someoneElse: "Someone else",
|
|
14
|
+
justNow: "just now",
|
|
15
|
+
saved: "Saved",
|
|
16
|
+
save: "Save",
|
|
17
|
+
cancel: "Cancel",
|
|
18
|
+
unsavedChanges: "Unsaved changes",
|
|
19
|
+
notSaved: "Not saved",
|
|
20
|
+
linesChanged: "The lines were changed by someone else",
|
|
21
|
+
linesReloaded: "The lines were reloaded. Your new order is kept: Save again to apply it.",
|
|
22
|
+
unsavedKept: "You have unsaved changes to this record.",
|
|
23
|
+
continueEditing: "Continue editing",
|
|
24
|
+
discard: "Discard",
|
|
25
|
+
unsavedRestored: "Your unsaved changes were restored.",
|
|
26
|
+
keep: "Keep",
|
|
27
|
+
savingSubject: (subject) => `Saving ${subject}...`,
|
|
28
|
+
subjectSaved: (subject) => `${subject} saved`,
|
|
29
|
+
changedBySomeoneElse: "Changed by someone else",
|
|
30
|
+
couldNotSave: "Could not save",
|
|
31
|
+
recordActions: "Record actions",
|
|
32
|
+
edit: "Edit",
|
|
33
|
+
delete: "Delete",
|
|
34
|
+
nothingYet: "Nothing here yet.",
|
|
35
|
+
couldNotLoadRelated: "Could not load",
|
|
36
|
+
relatedRecords: "Related records",
|
|
37
|
+
lines: "Lines",
|
|
38
|
+
linesHelp: "What the document bills for. Drag the handle, or use the menu, to change the order; Save applies it."
|
|
39
|
+
},
|
|
40
|
+
nl: {
|
|
41
|
+
details: "Details",
|
|
42
|
+
history: "Geschiedenis",
|
|
43
|
+
comments: "Opmerkingen",
|
|
44
|
+
summary: "Overzicht",
|
|
45
|
+
loading: "Laden",
|
|
46
|
+
fetching: "Het record wordt opgehaald.",
|
|
47
|
+
noRecord: "Dit record bestaat niet, of je mag het niet zien.",
|
|
48
|
+
couldNotLoad: "Kon het record niet laden",
|
|
49
|
+
someoneElse: "Iemand anders",
|
|
50
|
+
justNow: "zojuist",
|
|
51
|
+
saved: "Bewaard",
|
|
52
|
+
save: "Bewaren",
|
|
53
|
+
cancel: "Annuleren",
|
|
54
|
+
unsavedChanges: "Niet bewaarde wijzigingen",
|
|
55
|
+
notSaved: "Niet bewaard",
|
|
56
|
+
linesChanged: "De regels zijn door iemand anders gewijzigd",
|
|
57
|
+
linesReloaded: "De regels zijn opnieuw geladen. Je nieuwe volgorde is behouden: bewaar opnieuw om hem toe te passen.",
|
|
58
|
+
unsavedKept: "Je hebt niet bewaarde wijzigingen in dit record.",
|
|
59
|
+
continueEditing: "Verder bewerken",
|
|
60
|
+
discard: "Weggooien",
|
|
61
|
+
unsavedRestored: "Je niet bewaarde wijzigingen zijn teruggezet.",
|
|
62
|
+
keep: "Houden",
|
|
63
|
+
savingSubject: (subject) => `${subject} bewaren...`,
|
|
64
|
+
subjectSaved: (subject) => `${subject} bewaard`,
|
|
65
|
+
changedBySomeoneElse: "Gewijzigd door iemand anders",
|
|
66
|
+
couldNotSave: "Kon niet bewaren",
|
|
67
|
+
recordActions: "Recordacties",
|
|
68
|
+
edit: "Bewerken",
|
|
69
|
+
delete: "Verwijderen",
|
|
70
|
+
nothingYet: "Hier staat nog niets.",
|
|
71
|
+
couldNotLoadRelated: "Kon niet laden",
|
|
72
|
+
relatedRecords: "Gerelateerde records",
|
|
73
|
+
lines: "Regels",
|
|
74
|
+
linesHelp: "Waar het document voor factureert. Sleep de greep, of gebruik het menu, om de volgorde te wijzigen; Bewaren past hem toe."
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
//#endregion
|
|
78
|
+
export { text };
|
package/dist/app/record-page.js
CHANGED
|
@@ -29,6 +29,7 @@ import { KeyFacts } from "../record-view/key-facts.js";
|
|
|
29
29
|
import { fromDraft, isRequiredOnEdit } from "../live/field-values.js";
|
|
30
30
|
import { fieldHelp, helpShown, recordShown, whenShown } from "./record-mode/record-mode.js";
|
|
31
31
|
import { isChosen, keyValue } from "./create/create-values.js";
|
|
32
|
+
import { text } from "./messages.js";
|
|
32
33
|
import { ErrorBanner } from "./error-banner.js";
|
|
33
34
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
34
35
|
import { shownWidget } from "./field-types/shown-widget.js";
|
|
@@ -41,6 +42,7 @@ import { can, fieldAccess } from "./permissions.js";
|
|
|
41
42
|
import { ValueBadge } from "./value-badge.js";
|
|
42
43
|
import { FieldValue } from "./pages/field-value.js";
|
|
43
44
|
import { namedIcon } from "./named-icon.js";
|
|
45
|
+
import { text as text$1 } from "./record-messages.js";
|
|
44
46
|
import { useDeleteFlow } from "./delete/use-delete-flow.js";
|
|
45
47
|
import { useRestore } from "./delete/use-restore.js";
|
|
46
48
|
import { auditField } from "../live/audit-fields.js";
|
|
@@ -79,19 +81,20 @@ const layoutFor = (model, view, read) => {
|
|
|
79
81
|
if (view && view.layout.length > 0) return view.layout;
|
|
80
82
|
const shown = Object.values(model.fields).filter((field) => !hidden(model, field.name));
|
|
81
83
|
const editable = shown.filter((field) => !field.readOnly);
|
|
84
|
+
const fields = (editable.length > 0 && !read ? editable : shown).map((field) => field.name);
|
|
82
85
|
return [
|
|
83
86
|
{
|
|
84
87
|
kind: "section",
|
|
85
|
-
title: "
|
|
86
|
-
fields
|
|
88
|
+
title: text$1("details"),
|
|
89
|
+
fields
|
|
87
90
|
},
|
|
88
91
|
...model.history ? [{
|
|
89
92
|
kind: "history",
|
|
90
|
-
title: "
|
|
93
|
+
title: text$1("history")
|
|
91
94
|
}] : [],
|
|
92
95
|
...model.comments ? [{
|
|
93
96
|
kind: "comments",
|
|
94
|
-
title: "
|
|
97
|
+
title: text$1("comments")
|
|
95
98
|
}] : []
|
|
96
99
|
];
|
|
97
100
|
};
|
|
@@ -99,25 +102,21 @@ const RecordPage = ({ resource, recordKey }) => {
|
|
|
99
102
|
const { resources } = useAdminMeta();
|
|
100
103
|
const model = resources[resource];
|
|
101
104
|
const record = useOpenedRecord(resource, recordKey);
|
|
102
|
-
if (!model) return /* @__PURE__ */
|
|
103
|
-
title: "
|
|
104
|
-
children:
|
|
105
|
-
"There is no resource called ",
|
|
106
|
-
resource,
|
|
107
|
-
"."
|
|
108
|
-
]
|
|
105
|
+
if (!model) return /* @__PURE__ */ jsx(Notice, {
|
|
106
|
+
title: text("notFound"),
|
|
107
|
+
children: text("noResource", resource)
|
|
109
108
|
});
|
|
110
109
|
if (record.isPending) return /* @__PURE__ */ jsx(Notice, {
|
|
111
|
-
title: "
|
|
112
|
-
children: "
|
|
110
|
+
title: text$1("loading"),
|
|
111
|
+
children: text$1("fetching")
|
|
113
112
|
});
|
|
114
113
|
if (record.error) return /* @__PURE__ */ jsx("div", {
|
|
115
114
|
className: "p-8",
|
|
116
115
|
children: isApiError(record.error) && record.error.status === 404 ? /* @__PURE__ */ jsx(Notice, {
|
|
117
|
-
title: "
|
|
118
|
-
children: "
|
|
116
|
+
title: text("notFound"),
|
|
117
|
+
children: text$1("noRecord")
|
|
119
118
|
}) : /* @__PURE__ */ jsx(ErrorBanner, {
|
|
120
|
-
title: "
|
|
119
|
+
title: text$1("couldNotLoad"),
|
|
121
120
|
error: record.error
|
|
122
121
|
})
|
|
123
122
|
});
|
|
@@ -241,11 +240,11 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
241
240
|
wide: Boolean(hint(name)?.multiline || hint(name)?.markdown),
|
|
242
241
|
conflict: conflict === void 0 ? void 0 : {
|
|
243
242
|
user: {
|
|
244
|
-
name: "
|
|
243
|
+
name: text$1("someoneElse"),
|
|
245
244
|
initials: "?",
|
|
246
245
|
hue: 40
|
|
247
246
|
},
|
|
248
|
-
ago: "
|
|
247
|
+
ago: text$1("justNow"),
|
|
249
248
|
theirValue: conflict || "—",
|
|
250
249
|
onKeepMine: () => editor.keepMine(name),
|
|
251
250
|
onUseTheirs: () => editor.useTheirs(name)
|
|
@@ -295,7 +294,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
295
294
|
};
|
|
296
295
|
const firstSidebar = layout.findIndex((item) => item.kind === "sidebar");
|
|
297
296
|
const summary = /* @__PURE__ */ jsx(MetadataCard, {
|
|
298
|
-
title: "
|
|
297
|
+
title: text$1("summary"),
|
|
299
298
|
rows: sidebarFields.flatMap((name) => {
|
|
300
299
|
const field = model.fields[name];
|
|
301
300
|
if (!field || !visible(name)) return [];
|
|
@@ -347,17 +346,17 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
347
346
|
loading: editor.saving,
|
|
348
347
|
disabled: !editor.dirty && editor.phase !== "saved",
|
|
349
348
|
onClick: () => void save(),
|
|
350
|
-
children: [editor.phase === "saved" && /* @__PURE__ */ jsx(Check, { className: "size-4" }), editor.phase === "saved" ? "
|
|
349
|
+
children: [editor.phase === "saved" && /* @__PURE__ */ jsx(Check, { className: "size-4" }), editor.phase === "saved" ? text$1("saved") : text$1("save")]
|
|
351
350
|
}),
|
|
352
351
|
leavesForm && /* @__PURE__ */ jsx(AppLink, {
|
|
353
352
|
to: readAddress,
|
|
354
353
|
className: cn(buttonClass(), "max-md:min-h-11 md:w-full"),
|
|
355
354
|
onClick: (event) => inApp(event) && editor.discard(),
|
|
356
|
-
children: "
|
|
355
|
+
children: text$1("cancel")
|
|
357
356
|
}),
|
|
358
357
|
editor.dirty && /* @__PURE__ */ jsx("span", {
|
|
359
358
|
className: "text-xs text-muted-foreground max-md:order-first",
|
|
360
|
-
children: "
|
|
359
|
+
children: text$1("unsavedChanges")
|
|
361
360
|
})
|
|
362
361
|
] });
|
|
363
362
|
const mainItem = (item, index) => {
|
|
@@ -447,46 +446,46 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
447
446
|
editor.message && /* @__PURE__ */ jsx("div", {
|
|
448
447
|
className: "mt-4",
|
|
449
448
|
children: /* @__PURE__ */ jsx(ErrorBanner, {
|
|
450
|
-
title: "
|
|
449
|
+
title: text$1("notSaved"),
|
|
451
450
|
error: new Error(editor.message)
|
|
452
451
|
})
|
|
453
452
|
}),
|
|
454
453
|
editor.linesConflict && /* @__PURE__ */ jsx("div", {
|
|
455
454
|
className: "mt-4",
|
|
456
455
|
children: /* @__PURE__ */ jsx(ErrorBanner, {
|
|
457
|
-
title: "
|
|
458
|
-
error:
|
|
456
|
+
title: text$1("linesChanged"),
|
|
457
|
+
error: new Error(text$1("linesReloaded"))
|
|
459
458
|
})
|
|
460
459
|
}),
|
|
461
460
|
shown !== "form" && offersEdit && editor.dirty && /* @__PURE__ */ jsxs("div", {
|
|
462
461
|
className: "mt-4 flex items-center justify-between gap-3 rounded-lg border bg-surface px-3 py-2",
|
|
463
462
|
role: "status",
|
|
464
|
-
children: [/* @__PURE__ */ jsx("span", { children: "
|
|
463
|
+
children: [/* @__PURE__ */ jsx("span", { children: text$1("unsavedKept") }), /* @__PURE__ */ jsxs("span", {
|
|
465
464
|
className: "flex gap-2",
|
|
466
465
|
children: [/* @__PURE__ */ jsx(AppLink, {
|
|
467
466
|
to: editAddress,
|
|
468
467
|
className: buttonClass({ size: "sm" }),
|
|
469
|
-
children: "
|
|
468
|
+
children: text$1("continueEditing")
|
|
470
469
|
}), /* @__PURE__ */ jsx(Button, {
|
|
471
470
|
size: "sm",
|
|
472
471
|
onClick: editor.discard,
|
|
473
|
-
children: "
|
|
472
|
+
children: text$1("discard")
|
|
474
473
|
})]
|
|
475
474
|
})]
|
|
476
475
|
}),
|
|
477
476
|
shown === "form" && editor.restored && /* @__PURE__ */ jsxs("div", {
|
|
478
477
|
className: "mt-4 flex items-center justify-between gap-3 rounded-lg border bg-surface px-3 py-2",
|
|
479
478
|
role: "status",
|
|
480
|
-
children: [/* @__PURE__ */ jsx("span", { children: "
|
|
479
|
+
children: [/* @__PURE__ */ jsx("span", { children: text$1("unsavedRestored") }), /* @__PURE__ */ jsxs("span", {
|
|
481
480
|
className: "flex gap-2",
|
|
482
481
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
483
482
|
size: "sm",
|
|
484
483
|
onClick: editor.dismissRestored,
|
|
485
|
-
children: "
|
|
484
|
+
children: text$1("keep")
|
|
486
485
|
}), /* @__PURE__ */ jsx(Button, {
|
|
487
486
|
size: "sm",
|
|
488
487
|
onClick: editor.discard,
|
|
489
|
-
children: "
|
|
488
|
+
children: text$1("discard")
|
|
490
489
|
})]
|
|
491
490
|
})]
|
|
492
491
|
}),
|
|
@@ -541,7 +540,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
541
540
|
return index === firstSidebar ? /* @__PURE__ */ jsx(Fragment, { children: summary }, "summary") : null;
|
|
542
541
|
}),
|
|
543
542
|
/* @__PURE__ */ jsx(MetadataCard, {
|
|
544
|
-
title: "
|
|
543
|
+
title: text$1("details"),
|
|
545
544
|
rows: [auditField(model, "created"), updatedAt].flatMap((name) => {
|
|
546
545
|
const field = name ? model.fields[name] : void 0;
|
|
547
546
|
if (!name || !field || !visible(name)) return [];
|
|
@@ -11,6 +11,7 @@ import { relatedHandOrder } from "./hand-order/hand-order.js";
|
|
|
11
11
|
import { relatedOpens } from "./related-opens.js";
|
|
12
12
|
import { EmbeddedDisplaySwitch, EmbeddedList } from "./embedded-list/embedded-list.js";
|
|
13
13
|
import { useEmbeddedDisplay } from "./embedded-list/use-embedded-display.js";
|
|
14
|
+
import { text } from "./record-messages.js";
|
|
14
15
|
import { HandSortedList } from "./hand-order/hand-sorted-list.js";
|
|
15
16
|
import { SortButton } from "./hand-order/sort-button.js";
|
|
16
17
|
import { useState } from "react";
|
|
@@ -71,9 +72,9 @@ const Loading = () => /* @__PURE__ */ jsx("div", {
|
|
|
71
72
|
className: "flex flex-col gap-2",
|
|
72
73
|
children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }, index))
|
|
73
74
|
});
|
|
74
|
-
const Empty = ({
|
|
75
|
+
const Empty = ({ empty }) => /* @__PURE__ */ jsx("p", {
|
|
75
76
|
className: "text-muted-foreground",
|
|
76
|
-
children:
|
|
77
|
+
children: empty ?? text("nothingYet")
|
|
77
78
|
});
|
|
78
79
|
/**
|
|
79
80
|
* A record page section listing the first records of another resource that point at this record, directly or through
|
|
@@ -130,9 +131,9 @@ const RelatedSection = ({ item, value, owner }) => {
|
|
|
130
131
|
help: item.help,
|
|
131
132
|
actions,
|
|
132
133
|
children: match.isPending ? /* @__PURE__ */ jsx(Loading, {}) : match.error ? /* @__PURE__ */ jsx(ErrorBanner, {
|
|
133
|
-
title: "
|
|
134
|
+
title: text("couldNotLoadRelated"),
|
|
134
135
|
error: match.error
|
|
135
|
-
}) : match.filter === null || match.filter === void 0 ? /* @__PURE__ */ jsx(Empty, {
|
|
136
|
+
}) : match.filter === null || match.filter === void 0 ? /* @__PURE__ */ jsx(Empty, { empty: item.empty }) : /* @__PURE__ */ jsx(EmbeddedList, {
|
|
136
137
|
model,
|
|
137
138
|
view,
|
|
138
139
|
filter: match.filter,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useToast } from "../toasts/toast-provider.js";
|
|
2
2
|
import { saveFailureDescription } from "./field-errors.js";
|
|
3
|
+
import { text } from "./record-messages.js";
|
|
3
4
|
import { useEffect, useRef, useState } from "react";
|
|
4
5
|
import { isApiError } from "@protobase/client";
|
|
5
6
|
//#region src/app/use-live-save.ts
|
|
@@ -18,7 +19,7 @@ const useLiveSave = (mode, subject) => {
|
|
|
18
19
|
const run = async (action) => {
|
|
19
20
|
const id = mode === "toast" ? toast.show({
|
|
20
21
|
state: "loading",
|
|
21
|
-
title:
|
|
22
|
+
title: text("savingSubject", subject)
|
|
22
23
|
}) : void 0;
|
|
23
24
|
setMessage(void 0);
|
|
24
25
|
if (mode === "button") setPhase("saving");
|
|
@@ -26,7 +27,7 @@ const useLiveSave = (mode, subject) => {
|
|
|
26
27
|
await action();
|
|
27
28
|
if (id !== void 0) toast.update(id, {
|
|
28
29
|
state: "success",
|
|
29
|
-
title:
|
|
30
|
+
title: text("subjectSaved", subject)
|
|
30
31
|
});
|
|
31
32
|
if (mode === "button") {
|
|
32
33
|
setPhase("saved");
|
|
@@ -35,7 +36,7 @@ const useLiveSave = (mode, subject) => {
|
|
|
35
36
|
return true;
|
|
36
37
|
} catch (error) {
|
|
37
38
|
if (!isApiError(error)) throw error;
|
|
38
|
-
const title = error.status === 412 ? "
|
|
39
|
+
const title = error.status === 412 ? text("changedBySomeoneElse") : text("couldNotSave");
|
|
39
40
|
const description = saveFailureDescription(error);
|
|
40
41
|
if (id !== void 0) toast.update(id, {
|
|
41
42
|
state: "error",
|
|
@@ -3,6 +3,7 @@ import { formatInt, formatPercent, formatSigned } from "../format/number.js";
|
|
|
3
3
|
import { formatDate, formatDateTime, formatRelativeTime } from "../format/date.js";
|
|
4
4
|
import { userById } from "../mocks/users.js";
|
|
5
5
|
import { fileShown } from "../live/file-values.js";
|
|
6
|
+
import { text } from "./messages.js";
|
|
6
7
|
import { enumLabel } from "@protobase/schema";
|
|
7
8
|
//#region src/data-grid/cell-text.ts
|
|
8
9
|
/** A relation cell's text: the related record's name when the row carries it, else its key. */
|
|
@@ -21,7 +22,7 @@ const cellText = (spec, value) => {
|
|
|
21
22
|
case "percent": return Number(value) === 0 ? "—" : formatPercent(Number(value));
|
|
22
23
|
case "status": return enumLabel(String(value), spec.labels);
|
|
23
24
|
case "relation": return relationLabel(value);
|
|
24
|
-
case "boolean": return value ? void 0 : "
|
|
25
|
+
case "boolean": return value ? void 0 : text("no");
|
|
25
26
|
case "file": return fileShown(value)?.name ?? "—";
|
|
26
27
|
case "user": return userById(String(value)).name;
|
|
27
28
|
}
|
package/dist/data-grid/cells.js
CHANGED
|
@@ -5,6 +5,7 @@ import { viewerZone, zoneLabel } from "../format/zone.js";
|
|
|
5
5
|
import { formatDateTime } from "../format/date.js";
|
|
6
6
|
import { userById } from "../mocks/users.js";
|
|
7
7
|
import { fileShown, shownImage } from "../live/file-values.js";
|
|
8
|
+
import { text } from "./messages.js";
|
|
8
9
|
import { cellText } from "./cell-text.js";
|
|
9
10
|
import { FileImage } from "../live/file-image.js";
|
|
10
11
|
import { ContactLink } from "../live/contact-link.js";
|
|
@@ -19,50 +20,50 @@ const muted = (text) => /* @__PURE__ */ jsx("span", {
|
|
|
19
20
|
});
|
|
20
21
|
const renderCell = (spec, value) => {
|
|
21
22
|
if (value === null || value === void 0) return muted("—");
|
|
22
|
-
const text = cellText(spec, value) ?? "";
|
|
23
|
+
const text$1 = cellText(spec, value) ?? "";
|
|
23
24
|
switch (spec.kind) {
|
|
24
25
|
case "text": return spec.link ? /* @__PURE__ */ jsx(ContactLink, {
|
|
25
26
|
format: spec.link,
|
|
26
|
-
value: text,
|
|
27
|
+
value: text$1,
|
|
27
28
|
className: "block min-w-0 truncate"
|
|
28
|
-
}) : /* @__PURE__ */ jsx(TruncatedText, { text });
|
|
29
|
+
}) : /* @__PURE__ */ jsx(TruncatedText, { text: text$1 });
|
|
29
30
|
case "id": return /* @__PURE__ */ jsx(TruncatedText, {
|
|
30
|
-
text,
|
|
31
|
+
text: text$1,
|
|
31
32
|
className: "font-mono text-xs text-muted-foreground"
|
|
32
33
|
});
|
|
33
34
|
case "money": return /* @__PURE__ */ jsx("span", {
|
|
34
35
|
className: "tabular-nums",
|
|
35
|
-
children: text
|
|
36
|
+
children: text$1
|
|
36
37
|
});
|
|
37
38
|
case "date": return /* @__PURE__ */ jsx("span", {
|
|
38
39
|
className: "tabular-nums text-muted-foreground",
|
|
39
|
-
children: text
|
|
40
|
+
children: text$1
|
|
40
41
|
});
|
|
41
42
|
case "datetime": return /* @__PURE__ */ jsx("span", {
|
|
42
43
|
className: "tabular-nums text-muted-foreground",
|
|
43
|
-
title: spec.relative ? formatDateTime(Number(value)) :
|
|
44
|
-
children: text
|
|
44
|
+
title: spec.relative ? formatDateTime(Number(value)) : text("shownIn", zoneLabel(Number(value), viewerZone())),
|
|
45
|
+
children: text$1
|
|
45
46
|
});
|
|
46
47
|
case "number": return /* @__PURE__ */ jsx("span", {
|
|
47
48
|
className: "tabular-nums",
|
|
48
|
-
children: text
|
|
49
|
+
children: text$1
|
|
49
50
|
});
|
|
50
51
|
case "signed": return /* @__PURE__ */ jsx("span", {
|
|
51
52
|
className: cn("tabular-nums", Number(value) < 0 ? "text-danger-text" : "text-success-text"),
|
|
52
|
-
children: text
|
|
53
|
+
children: text$1
|
|
53
54
|
});
|
|
54
55
|
case "percent": return Number(value) === 0 ? muted("—") : /* @__PURE__ */ jsx("span", {
|
|
55
56
|
className: "tabular-nums",
|
|
56
|
-
children: text
|
|
57
|
+
children: text$1
|
|
57
58
|
});
|
|
58
59
|
case "status": return /* @__PURE__ */ jsx(Badge, {
|
|
59
60
|
tone: spec.tones?.[String(value)] ?? "neutral",
|
|
60
61
|
color: valueColor(spec.colors, value),
|
|
61
62
|
shape: spec.shape,
|
|
62
|
-
children: text
|
|
63
|
+
children: text$1
|
|
63
64
|
});
|
|
64
65
|
case "relation": return /* @__PURE__ */ jsx(TruncatedText, {
|
|
65
|
-
text,
|
|
66
|
+
text: text$1,
|
|
66
67
|
className: "font-medium text-primary-text hover:underline"
|
|
67
68
|
});
|
|
68
69
|
case "boolean": return value ? /* @__PURE__ */ jsx("span", {
|
|
@@ -71,7 +72,7 @@ const renderCell = (spec, value) => {
|
|
|
71
72
|
className: "size-3",
|
|
72
73
|
strokeWidth: 3
|
|
73
74
|
})
|
|
74
|
-
}) : muted("
|
|
75
|
+
}) : muted(text("no"));
|
|
75
76
|
case "file": {
|
|
76
77
|
const file = fileShown(value);
|
|
77
78
|
if (!file) return muted("—");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { formatApproxRows, formatInt } from "../format/number.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/data-grid/grid-footer.tsx
|
|
5
6
|
/** How many rows match and, with rows selected, how many and what can be done with them. */
|
|
@@ -9,9 +10,9 @@ const GridFooter = ({ total, selectedCount, bulkActions = [], className }) => /*
|
|
|
9
10
|
className: "flex items-center tabular-nums",
|
|
10
11
|
children: [
|
|
11
12
|
formatApproxRows(total),
|
|
12
|
-
selectedCount > 0 && /* @__PURE__ */
|
|
13
|
+
selectedCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
13
14
|
className: "ml-3 font-medium text-primary-text",
|
|
14
|
-
children:
|
|
15
|
+
children: text("selected", formatInt(selectedCount))
|
|
15
16
|
}),
|
|
16
17
|
selectedCount > 0 && bulkActions.map((action) => /* @__PURE__ */ jsx("button", {
|
|
17
18
|
type: "button",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { gridTemplate } from "./grid-template.js";
|
|
4
5
|
import { isRightAligned } from "./column-spec.js";
|
|
5
6
|
import { HeaderMenu, unsortableText } from "./header-menu.js";
|
|
@@ -16,7 +17,7 @@ const HeaderLabel = ({ spec }) => /* @__PURE__ */ jsxs(Fragment, { children: [/*
|
|
|
16
17
|
children: spec.header
|
|
17
18
|
}), spec.highlight && /* @__PURE__ */ jsx("span", {
|
|
18
19
|
className: "rounded bg-primary px-1 text-[10px] font-semibold uppercase leading-4 text-primary-foreground",
|
|
19
|
-
children: "
|
|
20
|
+
children: text("new")
|
|
20
21
|
})] });
|
|
21
22
|
const GridHeader = ({ cells, specs, gutter, selectedCount, selectable, trailing, minScale, pins = {}, lastPinned, scrolled, onTogglePin, onClearSelection }) => /* @__PURE__ */ jsxs("div", {
|
|
22
23
|
className: "grid h-8 shrink-0 items-center border-b bg-surface text-xs font-medium text-muted-foreground",
|
|
@@ -29,7 +30,7 @@ const GridHeader = ({ cells, specs, gutter, selectedCount, selectable, trailing,
|
|
|
29
30
|
children: [selectable && /* @__PURE__ */ jsx(Checkbox, {
|
|
30
31
|
checked: false,
|
|
31
32
|
indeterminate: selectedCount > 0,
|
|
32
|
-
label: selectedCount > 0 ? "
|
|
33
|
+
label: selectedCount > 0 ? text("clearSelection") : text("noRowsSelected"),
|
|
33
34
|
disabled: selectedCount === 0,
|
|
34
35
|
onChange: onClearSelection,
|
|
35
36
|
className: cn(selectedCount === 0 && "opacity-60")
|
|
@@ -50,7 +51,7 @@ const GridHeader = ({ cells, specs, gutter, selectedCount, selectable, trailing,
|
|
|
50
51
|
className: cn("group/head flex h-full min-w-0 flex-1 items-center gap-1 pl-3 pr-1 text-left", isRightAligned(spec.kind) && "justify-end", "hover:text-foreground", sorted && "text-foreground"),
|
|
51
52
|
children: [/* @__PURE__ */ jsx(HeaderLabel, { spec }), /* @__PURE__ */ jsx(SortIcon, { sorted })]
|
|
52
53
|
}) : /* @__PURE__ */ jsx("div", {
|
|
53
|
-
title: unsortableText,
|
|
54
|
+
title: unsortableText(),
|
|
54
55
|
className: cn("flex h-full min-w-0 flex-1 items-center gap-1 pl-3 pr-1 text-left", isRightAligned(spec.kind) && "justify-end"),
|
|
55
56
|
children: /* @__PURE__ */ jsx(HeaderLabel, { spec })
|
|
56
57
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -2,6 +2,7 @@ import { cn } from "../lib/cn.js";
|
|
|
2
2
|
import { Skeleton } from "../primitives/skeleton.js";
|
|
3
3
|
import { formatInt } from "../format/number.js";
|
|
4
4
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
5
|
+
import { text } from "./messages.js";
|
|
5
6
|
import { gridTemplate } from "./grid-template.js";
|
|
6
7
|
import { isRightAligned } from "./column-spec.js";
|
|
7
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -27,7 +28,7 @@ const Gutter = ({ width, index, selectable, selected, onToggle, edge }) => /* @_
|
|
|
27
28
|
style: { width },
|
|
28
29
|
children: [selectable && /* @__PURE__ */ jsx(Checkbox, {
|
|
29
30
|
checked: Boolean(selected),
|
|
30
|
-
label:
|
|
31
|
+
label: text("selectRow", index + 1)
|
|
31
32
|
}), width > 40 && /* @__PURE__ */ jsx("span", {
|
|
32
33
|
className: "min-w-6 text-right text-[11px] tabular-nums text-faint-foreground",
|
|
33
34
|
children: formatInt(index + 1)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
1
2
|
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
import { MoreHorizontal } from "lucide-react";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
//#region src/data-grid/header-menu.tsx
|
|
5
6
|
/** Why a column header does nothing when pressed: a list sorts only on the columns its view makes sortable. */
|
|
6
|
-
const unsortableText =
|
|
7
|
+
const unsortableText = () => text("unsortable");
|
|
7
8
|
/** The "..." in a column header: pinning the column to the left edge, and saying so when the list can't sort by it. */
|
|
8
9
|
const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
|
|
9
10
|
const [open, setOpen] = useState(false);
|
|
@@ -25,7 +26,7 @@ const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
|
|
|
25
26
|
className: "relative ml-auto shrink-0",
|
|
26
27
|
children: [/* @__PURE__ */ jsx("button", {
|
|
27
28
|
type: "button",
|
|
28
|
-
"aria-label":
|
|
29
|
+
"aria-label": text("columnMenu", header),
|
|
29
30
|
"aria-haspopup": "menu",
|
|
30
31
|
"aria-expanded": open,
|
|
31
32
|
onClick: () => setOpen(!open),
|
|
@@ -40,7 +41,7 @@ const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
|
|
|
40
41
|
role: "menuitem",
|
|
41
42
|
"aria-disabled": "true",
|
|
42
43
|
className: "block min-h-9 whitespace-nowrap px-3 py-1.5 text-muted-foreground",
|
|
43
|
-
children:
|
|
44
|
+
children: unsortableText()
|
|
44
45
|
})
|
|
45
46
|
}), /* @__PURE__ */ jsx("li", {
|
|
46
47
|
role: "none",
|
|
@@ -52,7 +53,7 @@ const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
|
|
|
52
53
|
setOpen(false);
|
|
53
54
|
onTogglePin();
|
|
54
55
|
},
|
|
55
|
-
children: pinned ? "
|
|
56
|
+
children: pinned ? text("unpin") : text("pinToLeft")
|
|
56
57
|
})
|
|
57
58
|
})]
|
|
58
59
|
})]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineMessages } from "../locale/messages.js";
|
|
2
|
+
//#region src/data-grid/messages.ts
|
|
3
|
+
const text = defineMessages({
|
|
4
|
+
en: {
|
|
5
|
+
unsortable: "This column can't be sorted",
|
|
6
|
+
columnMenu: (header) => `${header} column menu`,
|
|
7
|
+
unpin: "Unpin",
|
|
8
|
+
pinToLeft: "Pin to left",
|
|
9
|
+
selected: (count) => `${count} selected`,
|
|
10
|
+
no: "No",
|
|
11
|
+
shownIn: (zone) => `Shown in ${zone}`,
|
|
12
|
+
new: "New",
|
|
13
|
+
clearSelection: "Clear selection",
|
|
14
|
+
noRowsSelected: "No rows selected",
|
|
15
|
+
selectRow: (row) => `Select row ${row}`,
|
|
16
|
+
rowActions: "Row actions",
|
|
17
|
+
row: "row",
|
|
18
|
+
move: (label) => `Move ${label}`,
|
|
19
|
+
reorderInstructions: "To move a row, press Space on its handle, move it with the up and down arrow keys, and press Space again to drop it there. Escape puts it back.",
|
|
20
|
+
pickedUp: (name, row, rows) => `Picked up ${name}, row ${row} of ${rows}.`,
|
|
21
|
+
isOver: (name, row, rows) => `${name} is over row ${row} of ${rows}.`,
|
|
22
|
+
droppedAt: (name, row, rows) => `${name} was dropped at row ${row} of ${rows}.`,
|
|
23
|
+
putBack: (name) => `${name} was put back.`,
|
|
24
|
+
cancelled: (name, row) => `Moving ${name} was cancelled. It is back at row ${row}.`
|
|
25
|
+
},
|
|
26
|
+
nl: {
|
|
27
|
+
unsortable: "Op deze kolom kan niet worden gesorteerd",
|
|
28
|
+
columnMenu: (header) => `Kolommenu ${header}`,
|
|
29
|
+
unpin: "Losmaken",
|
|
30
|
+
pinToLeft: "Links vastzetten",
|
|
31
|
+
selected: (count) => `${count} geselecteerd`,
|
|
32
|
+
no: "Nee",
|
|
33
|
+
shownIn: (zone) => `Getoond in ${zone}`,
|
|
34
|
+
new: "Nieuw",
|
|
35
|
+
clearSelection: "Selectie wissen",
|
|
36
|
+
noRowsSelected: "Geen rijen geselecteerd",
|
|
37
|
+
selectRow: (row) => `Rij ${row} selecteren`,
|
|
38
|
+
rowActions: "Rijacties",
|
|
39
|
+
row: "rij",
|
|
40
|
+
move: (label) => `${label} verplaatsen`,
|
|
41
|
+
reorderInstructions: "Om een rij te verplaatsen, druk op Spatie op de greep, verplaats hem met de pijltjestoetsen omhoog en omlaag, en druk nogmaals op Spatie om hem daar neer te zetten. Escape zet hem terug.",
|
|
42
|
+
pickedUp: (name, row, rows) => `${name} opgepakt, rij ${row} van ${rows}.`,
|
|
43
|
+
isOver: (name, row, rows) => `${name} is boven rij ${row} van ${rows}.`,
|
|
44
|
+
droppedAt: (name, row, rows) => `${name} is neergezet op rij ${row} van ${rows}.`,
|
|
45
|
+
putBack: (name) => `${name} is teruggezet.`,
|
|
46
|
+
cancelled: (name, row) => `Verplaatsen van ${name} is geannuleerd. Hij staat weer op rij ${row}.`
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
//#endregion
|
|
50
|
+
export { text };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { RouteLink } from "../primitives/route-link.js";
|
|
3
|
+
import { text } from "./messages.js";
|
|
3
4
|
import { usePopoverMenu } from "../primitives/popover-menu.js";
|
|
4
5
|
import { MoreHorizontal } from "lucide-react";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -20,7 +21,7 @@ const RowMenu = ({ row, actions }) => {
|
|
|
20
21
|
children: [/* @__PURE__ */ jsx("button", {
|
|
21
22
|
ref: button,
|
|
22
23
|
type: "button",
|
|
23
|
-
"aria-label": "
|
|
24
|
+
"aria-label": text("rowActions"),
|
|
24
25
|
"aria-haspopup": "menu",
|
|
25
26
|
"aria-expanded": open,
|
|
26
27
|
onClick: () => setOpen(!open),
|