@marktiderman/genesis-studio 0.2.5 → 0.2.7
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/README.md +1 -1
- package/dist/index.js +65 -131
- package/dist/index.js.map +1 -1
- package/dist/standalone.css +1 -1
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @marktiderman/genesis-studio
|
|
2
2
|
|
|
3
|
-
**Genesis Data Studio**, the
|
|
3
|
+
**Genesis Data Studio**, the editor UI: a sidebar of the tables the signed-in user may see, and a
|
|
4
4
|
sheet of the selected one where a cell is edited in place. Every save is checked against the
|
|
5
5
|
version of the row it was read at and committed with its audit entry, and the outcome is on screen:
|
|
6
6
|
"Saved", with the audit entry, or "Not saved" and why.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/DataStudio.tsx
|
|
4
4
|
import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo8, useRef, useState as useState10 } from "react";
|
|
5
5
|
import { keepPreviousData as keepPreviousData2, useQuery as useQuery5, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
6
|
-
import { ChevronLeft
|
|
6
|
+
import { ChevronLeft, ChevronRight, Database, History as History2, Lock as Lock2, PencilLine, Plus as Plus3, Table2 as Table22, Trash2 as Trash22, Undo2, X as X4 } from "lucide-react";
|
|
7
7
|
import { GenesisProvider } from "@marktiderman/genesis-core/provider";
|
|
8
8
|
import {
|
|
9
9
|
encodeRecordKey as encodeRecordKey5,
|
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
keyFields as keyFields3
|
|
12
12
|
} from "@marktiderman/genesis-core/schema";
|
|
13
13
|
import { useSavedViews } from "@marktiderman/genesis-ui/data";
|
|
14
|
-
import { Button as
|
|
14
|
+
import { Button as Button8 } from "@marktiderman/genesis-ui/button";
|
|
15
15
|
import { Dialog as Dialog3, DialogContent as DialogContent3, DialogDescription as DialogDescription3, DialogHeader as DialogHeader3, DialogTitle as DialogTitle3 } from "@marktiderman/genesis-ui/dialog";
|
|
16
16
|
import { Alert as Alert2, AlertDescription as AlertDescription2, AlertTitle as AlertTitle2 } from "@marktiderman/genesis-ui/alert";
|
|
17
|
-
import { cn as
|
|
17
|
+
import { cn as cn3 } from "@marktiderman/genesis-ui/utils";
|
|
18
18
|
|
|
19
19
|
// src/router.ts
|
|
20
20
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -43,7 +43,7 @@ function locationFromSearchParams(params) {
|
|
|
43
43
|
...params.get(PARAM.record) ? { record: params.get(PARAM.record) } : {},
|
|
44
44
|
...VIEWS.includes(params.get(PARAM.view) ?? "") ? { view: params.get(PARAM.view) } : {},
|
|
45
45
|
...params.get(PARAM.by) ? { by: params.get(PARAM.by) } : {},
|
|
46
|
-
.../^\d{4}
|
|
46
|
+
.../^\d{4}-(0[1-9]|1[0-2])$/.test(params.get(PARAM.month) ?? "") ? { month: params.get(PARAM.month) } : {}
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
function searchParamsFromLocation(location, base) {
|
|
@@ -1731,12 +1731,12 @@ function KanbanView({
|
|
|
1731
1731
|
// src/views/CalendarView.tsx
|
|
1732
1732
|
import { useEffect as useEffect4, useMemo as useMemo5 } from "react";
|
|
1733
1733
|
import { keepPreviousData, useQuery as useQuery4 } from "@tanstack/react-query";
|
|
1734
|
-
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
1735
1734
|
import { encodeRecordKey as encodeRecordKey3, isDataWriteError as isDataWriteError3 } from "@marktiderman/genesis-core/schema";
|
|
1736
|
-
import {
|
|
1737
|
-
import { cn as cn3 } from "@marktiderman/genesis-ui/utils";
|
|
1735
|
+
import { EventCalendar, readEventTime as readEventTime2 } from "@marktiderman/genesis-ui/data";
|
|
1738
1736
|
|
|
1739
1737
|
// src/views/fields.ts
|
|
1738
|
+
import { deviceTimeZone } from "@marktiderman/genesis-core/dates";
|
|
1739
|
+
import { calendarWindow, readEventTime, timeStorage, windowBounds } from "@marktiderman/genesis-ui/data";
|
|
1740
1740
|
function boardFields(schema) {
|
|
1741
1741
|
return schema.fields.filter((f) => !f.array && Boolean(choicesFor(f)));
|
|
1742
1742
|
}
|
|
@@ -1746,58 +1746,20 @@ function dateFields(schema) {
|
|
|
1746
1746
|
function isDateOnly(field) {
|
|
1747
1747
|
return field.dbType === "date" || field.widget === "date";
|
|
1748
1748
|
}
|
|
1749
|
-
var DB_DATE = /^(\d{4}-\d{2}-\d{2})(?:[ T](\d{2}:\d{2}(?::\d{2})?)(\.\d+)?)?\s*(Z|[+-]\d{2}(?::?\d{2})?)?$/;
|
|
1750
|
-
function localDay(value, dateOnly) {
|
|
1751
|
-
if (typeof value !== "string") return null;
|
|
1752
|
-
const m = DB_DATE.exec(value.trim());
|
|
1753
|
-
if (!m) return null;
|
|
1754
|
-
if (dateOnly || !m[2]) return m[1];
|
|
1755
|
-
let zone = m[4] ?? "";
|
|
1756
|
-
if (/^[+-]\d{2}$/.test(zone)) zone += ":00";
|
|
1757
|
-
else if (/^[+-]\d{4}$/.test(zone)) zone = `${zone.slice(0, 3)}:${zone.slice(3)}`;
|
|
1758
|
-
const time = m[2].length === 5 ? `${m[2]}:00` : m[2];
|
|
1759
|
-
const ms = m[3] ? m[3].slice(0, 4).padEnd(4, "0") : "";
|
|
1760
|
-
const date = /* @__PURE__ */ new Date(`${m[1]}T${time}${ms}${zone}`);
|
|
1761
|
-
if (Number.isNaN(date.getTime())) return null;
|
|
1762
|
-
return dayKey(date);
|
|
1763
|
-
}
|
|
1764
1749
|
function dayKey(date) {
|
|
1765
1750
|
const pad = (n) => String(n).padStart(2, "0");
|
|
1766
1751
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
1767
1752
|
}
|
|
1768
|
-
function isZoned(field) {
|
|
1769
|
-
return !isDateOnly(field) && (field.dbType === void 0 || /with time zone|timestamptz/.test(field.dbType));
|
|
1770
|
-
}
|
|
1771
1753
|
function monthBounds(month, field) {
|
|
1772
|
-
|
|
1773
|
-
const start = new Date(y, m - 1, 1);
|
|
1774
|
-
const next = new Date(y, m, 1);
|
|
1775
|
-
return isZoned(field) ? { from: start.toISOString(), to: next.toISOString() } : { from: dayKey(start), to: dayKey(next) };
|
|
1754
|
+
return windowBounds(calendarWindow("month", `${month}-01`, { weekStartsOn: 1 }), timeStorage(field), deviceTimeZone());
|
|
1776
1755
|
}
|
|
1777
1756
|
function currentMonth(now = /* @__PURE__ */ new Date()) {
|
|
1778
1757
|
return dayKey(now).slice(0, 7);
|
|
1779
1758
|
}
|
|
1780
|
-
function shiftMonth(month, by) {
|
|
1781
|
-
const [y, m] = month.split("-").map(Number);
|
|
1782
|
-
return currentMonth(new Date(y, m - 1 + by, 1));
|
|
1783
|
-
}
|
|
1784
1759
|
|
|
1785
1760
|
// src/views/CalendarView.tsx
|
|
1786
|
-
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1761
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1787
1762
|
var CALENDAR_CAP = 200;
|
|
1788
|
-
var WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
1789
|
-
function weeksOf(month) {
|
|
1790
|
-
const [y, m] = month.split("-").map(Number);
|
|
1791
|
-
const first = new Date(y, m - 1, 1);
|
|
1792
|
-
const days = new Date(y, m, 0).getDate();
|
|
1793
|
-
const lead = (first.getDay() + 6) % 7;
|
|
1794
|
-
const cells = [
|
|
1795
|
-
...Array.from({ length: lead }, () => null),
|
|
1796
|
-
...Array.from({ length: days }, (_, i) => dayKey(new Date(y, m - 1, i + 1)))
|
|
1797
|
-
];
|
|
1798
|
-
while (cells.length % 7) cells.push(null);
|
|
1799
|
-
return Array.from({ length: cells.length / 7 }, (_, w) => cells.slice(w * 7, w * 7 + 7));
|
|
1800
|
-
}
|
|
1801
1763
|
function CalendarView({
|
|
1802
1764
|
provider,
|
|
1803
1765
|
schema,
|
|
@@ -1835,83 +1797,55 @@ function CalendarView({
|
|
|
1835
1797
|
if (signedOut) onSignedOut();
|
|
1836
1798
|
}, [signedOut, onSignedOut]);
|
|
1837
1799
|
const rows = query.data?.data ?? [];
|
|
1838
|
-
const byDay = useMemo5(() => {
|
|
1839
|
-
const map = /* @__PURE__ */ new Map();
|
|
1840
|
-
for (const row of rows) {
|
|
1841
|
-
const day = localDay(row[field.name], dateOnly);
|
|
1842
|
-
if (!day) continue;
|
|
1843
|
-
map.set(day, [...map.get(day) ?? [], row]);
|
|
1844
|
-
}
|
|
1845
|
-
return map;
|
|
1846
|
-
}, [rows, field.name, dateOnly]);
|
|
1847
|
-
const total = query.data?.total ?? 0;
|
|
1848
|
-
const today = dayKey(/* @__PURE__ */ new Date());
|
|
1849
|
-
const title = (/* @__PURE__ */ new Date(`${month}-01T00:00:00`)).toLocaleDateString(void 0, { month: "long", year: "numeric" });
|
|
1850
1800
|
const labelField = schema.labelField;
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1801
|
+
const events = useMemo5(
|
|
1802
|
+
() => rows.flatMap((row) => {
|
|
1803
|
+
const start = readEventTime2(row[field.name], { dateOnly });
|
|
1804
|
+
if (start === null) return [];
|
|
1805
|
+
const key = String(encodeRecordKey3(schema, row));
|
|
1806
|
+
return [{ key, title: displayText(labelField ? row[labelField] : void 0) ?? key, start, row }];
|
|
1807
|
+
}),
|
|
1808
|
+
[rows, field.name, dateOnly, schema, labelField]
|
|
1809
|
+
);
|
|
1810
|
+
const total = query.data?.total ?? 0;
|
|
1811
|
+
return /* @__PURE__ */ jsx8(
|
|
1812
|
+
EventCalendar,
|
|
1813
|
+
{
|
|
1814
|
+
events,
|
|
1815
|
+
layout: "month",
|
|
1816
|
+
weekStartsOn: 1,
|
|
1817
|
+
anchor: `${month}-01`,
|
|
1818
|
+
onAnchorChange: (day) => onMonthChange(day.slice(0, 7)),
|
|
1819
|
+
label: `${schema.label ?? schema.name} by ${fieldLabel(field)}`,
|
|
1820
|
+
toolbar: /* @__PURE__ */ jsxs7("span", { className: "text-xs text-muted-foreground", children: [
|
|
1858
1821
|
"by ",
|
|
1859
1822
|
fieldLabel(field)
|
|
1860
|
-
] })
|
|
1861
|
-
] }),
|
|
1862
|
-
total > rows.length && /* @__PURE__ */ jsxs7("p", { role: "status", className: "text-xs text-muted-foreground", "data-testid": "studio-calendar-capped", children: [
|
|
1863
|
-
"Showing the first ",
|
|
1864
|
-
rows.length.toLocaleString(),
|
|
1865
|
-
" of ",
|
|
1866
|
-
total.toLocaleString(),
|
|
1867
|
-
" rows this month. Filter the table to see the rest."
|
|
1868
|
-
] }),
|
|
1869
|
-
query.isError && !signedOut && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-sm text-destructive", children: query.error instanceof Error ? query.error.message : String(query.error) }),
|
|
1870
|
-
/* @__PURE__ */ jsx8("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs7("table", { className: "w-full min-w-[42rem] table-fixed border-collapse text-sm", children: [
|
|
1871
|
-
/* @__PURE__ */ jsxs7("caption", { className: "sr-only", children: [
|
|
1872
|
-
schema.label ?? schema.name,
|
|
1873
|
-
" by ",
|
|
1874
|
-
fieldLabel(field),
|
|
1875
|
-
", ",
|
|
1876
|
-
title
|
|
1877
1823
|
] }),
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
onClick: () => onOpenRecord(id),
|
|
1894
|
-
"data-testid": "studio-calendar-item",
|
|
1895
|
-
children: displayText(labelField ? row[labelField] : void 0) ?? id
|
|
1896
|
-
}
|
|
1897
|
-
) }, id);
|
|
1898
|
-
}) })
|
|
1899
|
-
] })
|
|
1900
|
-
},
|
|
1901
|
-
day ?? `pad-${w}-${d}`
|
|
1902
|
-
)) }, w)) })
|
|
1903
|
-
] }) })
|
|
1904
|
-
] });
|
|
1824
|
+
notice: /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
1825
|
+
total > rows.length && /* @__PURE__ */ jsxs7("p", { role: "status", className: "text-xs text-muted-foreground", "data-testid": "studio-calendar-capped", children: [
|
|
1826
|
+
"Showing the first ",
|
|
1827
|
+
rows.length.toLocaleString(),
|
|
1828
|
+
" of ",
|
|
1829
|
+
total.toLocaleString(),
|
|
1830
|
+
" rows in this view. Filter the table to see the rest."
|
|
1831
|
+
] }),
|
|
1832
|
+
query.isError && !signedOut && /* @__PURE__ */ jsx8("p", { role: "alert", className: "text-sm text-destructive", children: query.error instanceof Error ? query.error.message : String(query.error) })
|
|
1833
|
+
] }),
|
|
1834
|
+
loading: query.isPending,
|
|
1835
|
+
onSelect: (event) => onOpenRecord(event.key),
|
|
1836
|
+
testID: "studio-calendar"
|
|
1837
|
+
}
|
|
1838
|
+
);
|
|
1905
1839
|
}
|
|
1906
1840
|
|
|
1907
1841
|
// src/toolbar/ImportDialog.tsx
|
|
1908
1842
|
import { useMemo as useMemo6, useState as useState8 } from "react";
|
|
1909
1843
|
import { Upload } from "lucide-react";
|
|
1910
|
-
import { Button as
|
|
1844
|
+
import { Button as Button6 } from "@marktiderman/genesis-ui/button";
|
|
1911
1845
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@marktiderman/genesis-ui/dialog";
|
|
1912
1846
|
import { Label as Label2 } from "@marktiderman/genesis-ui/label";
|
|
1913
1847
|
import { Select as Select3, SelectContent as SelectContent3, SelectItem as SelectItem3, SelectTrigger as SelectTrigger3, SelectValue as SelectValue3 } from "@marktiderman/genesis-ui/select";
|
|
1914
|
-
import { Fragment as
|
|
1848
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1915
1849
|
var SKIP = "__genesis_skip__";
|
|
1916
1850
|
function ImportDialog({ provider, schema, onImported }) {
|
|
1917
1851
|
const [open, setOpen] = useState8(false);
|
|
@@ -1965,9 +1899,9 @@ function ImportDialog({ provider, schema, onImported }) {
|
|
|
1965
1899
|
}
|
|
1966
1900
|
}
|
|
1967
1901
|
const mapped = Object.values(mapping).filter((f) => f && f !== SKIP).length;
|
|
1968
|
-
return /* @__PURE__ */ jsxs8(
|
|
1902
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1969
1903
|
/* @__PURE__ */ jsx9(
|
|
1970
|
-
|
|
1904
|
+
Button6,
|
|
1971
1905
|
{
|
|
1972
1906
|
type: "button",
|
|
1973
1907
|
variant: "ghost",
|
|
@@ -2003,7 +1937,7 @@ function ImportDialog({ provider, schema, onImported }) {
|
|
|
2003
1937
|
}
|
|
2004
1938
|
)
|
|
2005
1939
|
] }),
|
|
2006
|
-
file && /* @__PURE__ */ jsxs8(
|
|
1940
|
+
file && /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
2007
1941
|
/* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
|
|
2008
1942
|
file.name,
|
|
2009
1943
|
": ",
|
|
@@ -2029,7 +1963,7 @@ function ImportDialog({ provider, schema, onImported }) {
|
|
|
2029
1963
|
"All ",
|
|
2030
1964
|
check.count,
|
|
2031
1965
|
" rows are ready to import."
|
|
2032
|
-
] }) : /* @__PURE__ */ jsxs8(
|
|
1966
|
+
] }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
2033
1967
|
/* @__PURE__ */ jsxs8("p", { className: "font-medium text-destructive", children: [
|
|
2034
1968
|
(check.failures ?? []).length,
|
|
2035
1969
|
" of ",
|
|
@@ -2045,8 +1979,8 @@ function ImportDialog({ provider, schema, onImported }) {
|
|
|
2045
1979
|
] }) }),
|
|
2046
1980
|
error && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-sm text-destructive", children: error }),
|
|
2047
1981
|
/* @__PURE__ */ jsxs8("div", { className: "flex justify-end gap-2", children: [
|
|
2048
|
-
/* @__PURE__ */ jsx9(
|
|
2049
|
-
/* @__PURE__ */ jsxs8(
|
|
1982
|
+
/* @__PURE__ */ jsx9(Button6, { type: "button", variant: "outline", disabled: busy || mapped === 0, onClick: () => void run(true), testID: "studio-import-check-button", children: busy ? "Checking\u2026" : "Check rows" }),
|
|
1983
|
+
/* @__PURE__ */ jsxs8(Button6, { type: "button", disabled: busy || !check?.ok, onClick: () => void run(false), testID: "studio-import-commit", children: [
|
|
2050
1984
|
"Import ",
|
|
2051
1985
|
check?.ok ? check.count : "",
|
|
2052
1986
|
" rows"
|
|
@@ -2060,7 +1994,7 @@ function ImportDialog({ provider, schema, onImported }) {
|
|
|
2060
1994
|
|
|
2061
1995
|
// src/toolbar/BulkEdit.tsx
|
|
2062
1996
|
import { useState as useState9 } from "react";
|
|
2063
|
-
import { Button as
|
|
1997
|
+
import { Button as Button7 } from "@marktiderman/genesis-ui/button";
|
|
2064
1998
|
import { Dialog as Dialog2, DialogContent as DialogContent2, DialogDescription as DialogDescription2, DialogHeader as DialogHeader2, DialogTitle as DialogTitle2 } from "@marktiderman/genesis-ui/dialog";
|
|
2065
1999
|
import { Input as Input3 } from "@marktiderman/genesis-ui/input";
|
|
2066
2000
|
import { Label as Label3 } from "@marktiderman/genesis-ui/label";
|
|
@@ -2140,8 +2074,8 @@ function BulkEditDialog({ schema, count, open, onOpenChange, onApply }) {
|
|
|
2140
2074
|
/* @__PURE__ */ jsx10("ul", { className: "mt-1 list-disc pl-5 text-xs", children: (result.failures ?? []).slice(0, 10).map((f) => /* @__PURE__ */ jsx10("li", { children: f.message }, f.index)) })
|
|
2141
2075
|
] }),
|
|
2142
2076
|
/* @__PURE__ */ jsxs9("div", { className: "flex justify-end gap-2", children: [
|
|
2143
|
-
/* @__PURE__ */ jsx10(
|
|
2144
|
-
/* @__PURE__ */ jsx10(
|
|
2077
|
+
/* @__PURE__ */ jsx10(Button7, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
2078
|
+
/* @__PURE__ */ jsx10(Button7, { type: "button", disabled: busy || !target, onClick: () => void apply(), testID: "studio-bulk-apply", children: busy ? "Saving\u2026" : `Set on ${count} row${count === 1 ? "" : "s"}` })
|
|
2145
2079
|
] })
|
|
2146
2080
|
] })
|
|
2147
2081
|
] })
|
|
@@ -2310,7 +2244,7 @@ function SheetView({
|
|
|
2310
2244
|
}
|
|
2311
2245
|
|
|
2312
2246
|
// src/DataStudio.tsx
|
|
2313
|
-
import { Fragment as
|
|
2247
|
+
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2314
2248
|
var PER_PAGE = 50;
|
|
2315
2249
|
var SEARCH_DEBOUNCE_MS = 300;
|
|
2316
2250
|
function isUnauthenticated(error) {
|
|
@@ -2459,14 +2393,14 @@ function StudioWorkspace({
|
|
|
2459
2393
|
);
|
|
2460
2394
|
}, []);
|
|
2461
2395
|
if (signedOut) {
|
|
2462
|
-
return /* @__PURE__ */ jsx12("div", { className:
|
|
2396
|
+
return /* @__PURE__ */ jsx12("div", { className: cn3("flex min-h-[320px] items-center justify-center p-6", className), "data-testid": "studio-sign-in", children: signIn ?? /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground", children: "Sign in to use the studio." }) });
|
|
2463
2397
|
}
|
|
2464
2398
|
const selected = router.location.resource;
|
|
2465
2399
|
const list = resources.data ?? [];
|
|
2466
|
-
return /* @__PURE__ */ jsxs11("div", { className:
|
|
2400
|
+
return /* @__PURE__ */ jsxs11("div", { className: cn3("w-full space-y-3", className), children: [
|
|
2467
2401
|
/* @__PURE__ */ jsxs11("div", { className: variant === "card" ? "overflow-hidden rounded-lg border bg-background" : "min-w-0", "data-testid": "studio", "data-variant": variant, children: [
|
|
2468
|
-
(title !== null || resources.isLoading || resources.isError) && /* @__PURE__ */ jsxs11("div", { className:
|
|
2469
|
-
title !== null && (variant === "card" ? /* @__PURE__ */ jsxs11(
|
|
2402
|
+
(title !== null || resources.isLoading || resources.isError) && /* @__PURE__ */ jsxs11("div", { className: cn3("flex items-center gap-2", variant === "card" ? "border-b px-3 py-2" : "pb-3"), children: [
|
|
2403
|
+
title !== null && (variant === "card" ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
|
|
2470
2404
|
/* @__PURE__ */ jsx12(Database, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
2471
2405
|
/* @__PURE__ */ jsx12("span", { className: "text-sm font-semibold", children: title })
|
|
2472
2406
|
] }) : /* @__PURE__ */ jsx12("h1", { className: "text-2xl font-semibold tracking-tight", children: title })),
|
|
@@ -2861,11 +2795,11 @@ function Sheet2({
|
|
|
2861
2795
|
selected.size > 0 && /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-1 rounded-md bg-muted px-2 text-sm", "data-testid": "studio-selection", children: [
|
|
2862
2796
|
selected.size,
|
|
2863
2797
|
" selected",
|
|
2864
|
-
schema.access.update && provider.updateMany && /* @__PURE__ */ jsxs11(
|
|
2798
|
+
schema.access.update && provider.updateMany && /* @__PURE__ */ jsxs11(Button8, { type: "button", variant: "ghost", size: "sm", testID: "studio-bulk-set", onClick: () => setBulkIds([...selected]), children: [
|
|
2865
2799
|
/* @__PURE__ */ jsx12(PencilLine, { className: "mr-1 h-3.5 w-3.5", "aria-hidden": true }),
|
|
2866
2800
|
"Set a field\u2026"
|
|
2867
2801
|
] }),
|
|
2868
|
-
schema.access.delete && provider.deleteMany && /* @__PURE__ */ jsxs11(
|
|
2802
|
+
schema.access.delete && provider.deleteMany && /* @__PURE__ */ jsxs11(Button8, { type: "button", variant: "ghost", size: "sm", className: "text-destructive", testID: "studio-bulk-delete", onClick: () => void deleteSelected(), children: [
|
|
2869
2803
|
/* @__PURE__ */ jsx12(Trash22, { className: "mr-1 h-3.5 w-3.5", "aria-hidden": true }),
|
|
2870
2804
|
"Delete"
|
|
2871
2805
|
] }),
|
|
@@ -2968,12 +2902,12 @@ function Sheet2({
|
|
|
2968
2902
|
total === 1 ? "" : "s"
|
|
2969
2903
|
] }),
|
|
2970
2904
|
pageCount > 1 && /* @__PURE__ */ jsxs11("span", { className: "ml-auto flex items-center gap-1", children: [
|
|
2971
|
-
/* @__PURE__ */ jsx12(
|
|
2905
|
+
/* @__PURE__ */ jsx12(Button8, { type: "button", variant: "ghost", size: "sm", disabled: page <= 1, onClick: () => navigate({ page: page - 1 }), "aria-label": "Previous page", children: /* @__PURE__ */ jsx12(ChevronLeft, { className: "h-3.5 w-3.5", "aria-hidden": true }) }),
|
|
2972
2906
|
"Page ",
|
|
2973
2907
|
page,
|
|
2974
2908
|
" of ",
|
|
2975
2909
|
pageCount,
|
|
2976
|
-
/* @__PURE__ */ jsx12(
|
|
2910
|
+
/* @__PURE__ */ jsx12(Button8, { type: "button", variant: "ghost", size: "sm", disabled: page >= pageCount, onClick: () => navigate({ page: page + 1 }), "aria-label": "Next page", children: /* @__PURE__ */ jsx12(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": true }) })
|
|
2977
2911
|
] })
|
|
2978
2912
|
] })
|
|
2979
2913
|
] })
|
|
@@ -3096,7 +3030,7 @@ function ActivityPanel({ entries, onUndo }) {
|
|
|
3096
3030
|
/* @__PURE__ */ jsx12(History2, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
3097
3031
|
"This session's edits",
|
|
3098
3032
|
entries.length > 0 && /* @__PURE__ */ jsx12("span", { className: "text-xs font-normal text-muted-foreground", children: entries.length }),
|
|
3099
|
-
entries[0] && /* @__PURE__ */ jsx12("span", { className:
|
|
3033
|
+
entries[0] && /* @__PURE__ */ jsx12("span", { className: cn3("ml-2 truncate text-xs font-normal", entries[0].outcome === "saved" ? "text-muted-foreground" : "text-destructive"), children: entries[0].message })
|
|
3100
3034
|
] }),
|
|
3101
3035
|
entries.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "px-3 py-3 text-xs text-muted-foreground", children: "Click a cell to edit it. Each save is checked against the latest version of the row, and audited." }) : /* @__PURE__ */ jsx12("ol", { className: "divide-y", "aria-live": "polite", children: entries.map((entry) => /* @__PURE__ */ jsxs11("li", { className: "px-3 py-2 text-xs", "data-testid": `studio-activity-${entry.outcome}`, children: [
|
|
3102
3036
|
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
|