xladmin 0.1.8 → 0.2.8

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/index.js CHANGED
@@ -45,11 +45,12 @@ function createXLAdminClient(transport) {
45
45
  payload != null ? payload : {}
46
46
  );
47
47
  },
48
- async getChoices(slug, fieldName, q, ids) {
48
+ async getChoices(slug, fieldName, q, ids, options) {
49
49
  return await transportGet(
50
50
  transport,
51
51
  `/xladmin/models/${slug}/fields/${fieldName}/choices/`,
52
52
  {
53
+ signal: options == null ? void 0 : options.signal,
53
54
  params: {
54
55
  ...q ? { q } : {},
55
56
  ...ids && ids.length > 0 ? { ids: ids.join(",") } : {}
@@ -57,11 +58,12 @@ function createXLAdminClient(transport) {
57
58
  }
58
59
  );
59
60
  },
60
- async getFilterChoices(slug, filterSlug, q, ids) {
61
+ async getFilterChoices(slug, filterSlug, q, ids, options) {
61
62
  return await transportGet(
62
63
  transport,
63
64
  `/xladmin/models/${slug}/filters/${filterSlug}/choices/`,
64
65
  {
66
+ signal: options == null ? void 0 : options.signal,
65
67
  params: {
66
68
  ...q ? { q } : {},
67
69
  ...ids && ids.length > 0 ? { ids: ids.join(",") } : {}
@@ -84,22 +86,22 @@ function createFetchXLAdminTransport(config) {
84
86
  throw new Error("Fetch API is not available in the current environment.");
85
87
  }
86
88
  return {
87
- get: async (url, options) => await requestJson(fetchImpl, config, "GET", url, void 0, options == null ? void 0 : options.params),
88
- post: async (url, body) => await requestJson(fetchImpl, config, "POST", url, body),
89
- patch: async (url, body) => await requestJson(fetchImpl, config, "PATCH", url, body),
90
- delete: async (url) => {
91
- await requestRaw(fetchImpl, config, "DELETE", url);
89
+ get: async (url, options) => await requestJson(fetchImpl, config, "GET", url, void 0, options == null ? void 0 : options.params, options == null ? void 0 : options.signal),
90
+ post: async (url, body, options) => await requestJson(fetchImpl, config, "POST", url, body, void 0, options == null ? void 0 : options.signal),
91
+ patch: async (url, body, options) => await requestJson(fetchImpl, config, "PATCH", url, body, void 0, options == null ? void 0 : options.signal),
92
+ delete: async (url, options) => {
93
+ await requestRaw(fetchImpl, config, "DELETE", url, void 0, void 0, options == null ? void 0 : options.signal);
92
94
  }
93
95
  };
94
96
  }
95
97
  async function transportGet(transport, url, options) {
96
98
  return unwrapTransportResponse(await transport.get(url, options));
97
99
  }
98
- async function transportPost(transport, url, body) {
99
- return unwrapTransportResponse(await transport.post(url, body));
100
+ async function transportPost(transport, url, body, options) {
101
+ return unwrapTransportResponse(await transport.post(url, body, options));
100
102
  }
101
- async function transportPatch(transport, url, body) {
102
- return unwrapTransportResponse(await transport.patch(url, body));
103
+ async function transportPatch(transport, url, body, options) {
104
+ return unwrapTransportResponse(await transport.patch(url, body, options));
103
105
  }
104
106
  function unwrapTransportResponse(response) {
105
107
  if (isWrappedTransportResponse(response)) {
@@ -110,15 +112,16 @@ function unwrapTransportResponse(response) {
110
112
  function isWrappedTransportResponse(response) {
111
113
  return typeof response === "object" && response !== null && "data" in response;
112
114
  }
113
- async function requestJson(fetchImpl, config, method, url, body, params) {
114
- const response = await requestRaw(fetchImpl, config, method, url, body, params);
115
+ async function requestJson(fetchImpl, config, method, url, body, params, signal) {
116
+ const response = await requestRaw(fetchImpl, config, method, url, body, params, signal);
115
117
  return await response.json();
116
118
  }
117
- async function requestRaw(fetchImpl, config, method, url, body, params) {
119
+ async function requestRaw(fetchImpl, config, method, url, body, params, signal) {
118
120
  var _a;
119
121
  const headers = await resolveHeaders(config.headers);
120
122
  const response = await fetchImpl(buildRequestUrl(config.baseUrl, url, params), {
121
123
  method,
124
+ signal,
122
125
  credentials: (_a = config.credentials) != null ? _a : "include",
123
126
  headers: {
124
127
  "Content-Type": "application/json",
@@ -134,6 +137,10 @@ async function requestRaw(fetchImpl, config, method, url, body, params) {
134
137
  detail = errorData.detail;
135
138
  }
136
139
  } catch {
140
+ const fallbackText = await response.text().catch(() => "");
141
+ if (fallbackText.trim()) {
142
+ detail = fallbackText.trim();
143
+ }
137
144
  }
138
145
  throw new Error(detail);
139
146
  }
@@ -175,9 +182,104 @@ function buildSearchParams(params) {
175
182
  return searchParams.toString();
176
183
  }
177
184
 
185
+ // src/router.tsx
186
+ import { createContext, useContext, useMemo, useRef, useSyncExternalStore } from "react";
187
+ import { jsx } from "react/jsx-runtime";
188
+ var XLAdminRouterContext = createContext(null);
189
+ var browserRouterInstance = null;
190
+ function XLAdminRouterProvider({ router, children }) {
191
+ return /* @__PURE__ */ jsx(XLAdminRouterContext.Provider, { value: router, children });
192
+ }
193
+ function createBrowserXLAdminRouter(browserWindow = window) {
194
+ return {
195
+ getLocation: () => ({
196
+ pathname: browserWindow.location.pathname,
197
+ search: browserWindow.location.search
198
+ }),
199
+ subscribe: (listener) => {
200
+ const handlePopState = () => listener();
201
+ browserWindow.addEventListener("popstate", handlePopState);
202
+ return () => {
203
+ browserWindow.removeEventListener("popstate", handlePopState);
204
+ };
205
+ },
206
+ push: (href) => {
207
+ browserWindow.history.pushState(browserWindow.history.state, "", href);
208
+ browserWindow.dispatchEvent(new PopStateEvent("popstate"));
209
+ },
210
+ replace: (href) => {
211
+ browserWindow.history.replaceState(browserWindow.history.state, "", href);
212
+ browserWindow.dispatchEvent(new PopStateEvent("popstate"));
213
+ },
214
+ back: () => {
215
+ browserWindow.history.back();
216
+ }
217
+ };
218
+ }
219
+ function useXLAdminRouter(router) {
220
+ const contextRouter = useContext(XLAdminRouterContext);
221
+ return useMemo(() => {
222
+ if (router) {
223
+ return router;
224
+ }
225
+ if (contextRouter) {
226
+ return contextRouter;
227
+ }
228
+ if (browserRouterInstance === null) {
229
+ browserRouterInstance = createBrowserXLAdminRouter();
230
+ }
231
+ return browserRouterInstance;
232
+ }, [contextRouter, router]);
233
+ }
234
+ function useXLAdminLocation(router) {
235
+ const resolvedRouter = useXLAdminRouter(router);
236
+ const snapshotRef = useRef(null);
237
+ const getSnapshot = useMemo(() => () => {
238
+ const nextLocation = resolvedRouter.getLocation();
239
+ const cachedLocation = snapshotRef.current;
240
+ if (cachedLocation !== null && cachedLocation.pathname === nextLocation.pathname && cachedLocation.search === nextLocation.search) {
241
+ return cachedLocation;
242
+ }
243
+ snapshotRef.current = nextLocation;
244
+ return nextLocation;
245
+ }, [resolvedRouter]);
246
+ return useSyncExternalStore(
247
+ resolvedRouter.subscribe,
248
+ getSnapshot,
249
+ getSnapshot
250
+ );
251
+ }
252
+ function buildUrlWithParams(pathname, query, sort, page, filters) {
253
+ const params = new URLSearchParams();
254
+ if (query) {
255
+ params.set("q", query);
256
+ }
257
+ if (sort) {
258
+ params.set("sort", sort);
259
+ }
260
+ if (page > 1) {
261
+ params.set("page", String(page));
262
+ }
263
+ for (const [key, value] of Object.entries(filters)) {
264
+ if (value) {
265
+ params.set(key, value);
266
+ }
267
+ }
268
+ return params.toString() ? `${pathname}?${params.toString()}` : pathname;
269
+ }
270
+ function handleNavLinkClick(event, { href, onClick, router }) {
271
+ onClick == null ? void 0 : onClick();
272
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
273
+ return;
274
+ }
275
+ event.preventDefault();
276
+ router.push(href);
277
+ }
278
+
178
279
  // src/components/FormDialog.tsx
179
- import { useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
280
+ import { useEffect as useEffect3, useMemo as useMemo4, useState as useState3 } from "react";
180
281
  import {
282
+ Alert,
181
283
  Box,
182
284
  Button,
183
285
  Dialog,
@@ -189,8 +291,8 @@ import { LocalizationProvider } from "@mui/x-date-pickers";
189
291
  import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
190
292
 
191
293
  // src/i18n.tsx
192
- import { createContext, useContext, useMemo } from "react";
193
- import { jsx } from "react/jsx-runtime";
294
+ import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
295
+ import { jsx as jsx2 } from "react/jsx-runtime";
194
296
  var messages = {
195
297
  ru: {
196
298
  admin_title: "Admin",
@@ -301,7 +403,7 @@ var messages = {
301
403
  staff_only: "Only staff users can access the admin."
302
404
  }
303
405
  };
304
- var AdminLocaleContext = createContext("ru");
406
+ var AdminLocaleContext = createContext2("ru");
305
407
  function normalizeAdminLocale(locale) {
306
408
  return locale === "en" ? "en" : "ru";
307
409
  }
@@ -314,15 +416,15 @@ function translateAdmin(locale, key, params) {
314
416
  });
315
417
  }
316
418
  function AdminLocaleProvider({ locale, children }) {
317
- const normalizedLocale = useMemo(() => normalizeAdminLocale(locale), [locale]);
318
- return /* @__PURE__ */ jsx(AdminLocaleContext.Provider, { value: normalizedLocale, children });
419
+ const normalizedLocale = useMemo2(() => normalizeAdminLocale(locale), [locale]);
420
+ return /* @__PURE__ */ jsx2(AdminLocaleContext.Provider, { value: normalizedLocale, children });
319
421
  }
320
422
  function useAdminLocale() {
321
- return normalizeAdminLocale(useContext(AdminLocaleContext));
423
+ return normalizeAdminLocale(useContext2(AdminLocaleContext));
322
424
  }
323
425
  function useAdminTranslation() {
324
426
  const locale = useAdminLocale();
325
- return useMemo(() => ((key, params) => translateAdmin(locale, key, params)), [locale]);
427
+ return useMemo2(() => ((key, params) => translateAdmin(locale, key, params)), [locale]);
326
428
  }
327
429
 
328
430
  // src/utils/adminFields.ts
@@ -475,7 +577,7 @@ function trimAdminValue(value, maxLength) {
475
577
  }
476
578
 
477
579
  // src/components/FieldEditor.tsx
478
- import { memo, useEffect, useMemo as useMemo2, useState } from "react";
580
+ import { memo, useCallback, useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
479
581
  import {
480
582
  Autocomplete,
481
583
  CircularProgress,
@@ -485,7 +587,93 @@ import {
485
587
  } from "@mui/material";
486
588
  import { DatePicker, DateTimePicker } from "@mui/x-date-pickers";
487
589
  import dayjs from "dayjs";
488
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
590
+
591
+ // src/hooks/useRemoteChoices.ts
592
+ import { useEffect, useRef as useRef2, useState } from "react";
593
+ function useRemoteChoices({
594
+ enabled,
595
+ debounceMs = 250,
596
+ initialItems = [],
597
+ resetKey,
598
+ queryKey,
599
+ load,
600
+ merge
601
+ }) {
602
+ const [items, setItems] = useState(initialItems);
603
+ const [isLoading, setIsLoading] = useState(false);
604
+ const previousResetKeyRef = useRef2(resetKey);
605
+ useEffect(() => {
606
+ if (previousResetKeyRef.current !== resetKey) {
607
+ previousResetKeyRef.current = resetKey;
608
+ setItems(initialItems);
609
+ setIsLoading(false);
610
+ return;
611
+ }
612
+ setItems((currentItems) => {
613
+ if (areChoiceArraysShallowEqual(currentItems, initialItems)) {
614
+ return currentItems;
615
+ }
616
+ if (currentItems.length > 0 && initialItems.length === 0) {
617
+ return currentItems;
618
+ }
619
+ return initialItems;
620
+ });
621
+ }, [initialItems, resetKey]);
622
+ useEffect(() => {
623
+ if (!enabled) {
624
+ setIsLoading(false);
625
+ return;
626
+ }
627
+ const controller = new AbortController();
628
+ const timeoutId = window.setTimeout(() => {
629
+ setIsLoading(true);
630
+ load(controller.signal).then((nextItems) => {
631
+ if (controller.signal.aborted) {
632
+ return;
633
+ }
634
+ setItems((current) => merge ? merge(current, nextItems) : nextItems);
635
+ }).catch((reason) => {
636
+ if (isAbortReason(reason) || controller.signal.aborted) {
637
+ return;
638
+ }
639
+ setItems((current) => current);
640
+ }).finally(() => {
641
+ if (controller.signal.aborted) {
642
+ return;
643
+ }
644
+ setIsLoading(false);
645
+ });
646
+ }, debounceMs);
647
+ return () => {
648
+ controller.abort();
649
+ window.clearTimeout(timeoutId);
650
+ };
651
+ }, [debounceMs, enabled, load, merge, queryKey]);
652
+ return { items, isLoading };
653
+ }
654
+ function areChoiceArraysShallowEqual(left, right) {
655
+ if (left === right) {
656
+ return true;
657
+ }
658
+ if (left.length !== right.length) {
659
+ return false;
660
+ }
661
+ for (let index = 0; index < left.length; index += 1) {
662
+ if (!Object.is(left[index], right[index])) {
663
+ return false;
664
+ }
665
+ }
666
+ return true;
667
+ }
668
+ function isAbortReason(reason) {
669
+ if (reason instanceof DOMException && reason.name === "AbortError") {
670
+ return true;
671
+ }
672
+ return typeof reason === "object" && reason !== null && "name" in reason && reason.name === "AbortError";
673
+ }
674
+
675
+ // src/components/FieldEditor.tsx
676
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
489
677
  var FieldEditor = memo(function FieldEditor2({
490
678
  field,
491
679
  value,
@@ -496,45 +684,44 @@ var FieldEditor = memo(function FieldEditor2({
496
684
  }) {
497
685
  var _a, _b, _c, _d;
498
686
  const t = useAdminTranslation();
499
- const [choices, setChoices] = useState([]);
500
- const [searchValue, setSearchValue] = useState("");
501
- const [isLoadingChoices, setIsLoadingChoices] = useState(false);
502
- const [jsonTextValue, setJsonTextValue] = useState(() => stringifyJsonValue(value));
503
- const [jsonError, setJsonError] = useState(null);
504
- const selectedIds = useMemo2(() => normalizeSelectedIds(value, field.is_relation_many), [field.is_relation_many, value]);
505
- useEffect(() => {
687
+ const [searchValue, setSearchValue] = useState2("");
688
+ const [jsonTextValue, setJsonTextValue] = useState2(() => stringifyJsonValue(value));
689
+ const [jsonError, setJsonError] = useState2(null);
690
+ const selectedIds = useMemo3(() => normalizeSelectedIds(value, field.is_relation_many), [field.is_relation_many, value]);
691
+ const loadChoices = useCallback(
692
+ async (signal) => {
693
+ const response = await client.getChoices(
694
+ slug,
695
+ field.name,
696
+ searchValue || void 0,
697
+ selectedIds,
698
+ { signal }
699
+ );
700
+ return response.items;
701
+ },
702
+ [client, field.name, searchValue, selectedIds, slug]
703
+ );
704
+ const { items: choices, isLoading: isLoadingChoices } = useRemoteChoices({
705
+ enabled: field.has_choices,
706
+ debounceMs: 250,
707
+ initialItems: [],
708
+ resetKey: `${slug}:${field.name}`,
709
+ queryKey: `${slug}:${field.name}:${searchValue}:${selectedIds.join(",")}`,
710
+ load: loadChoices,
711
+ merge: mergeChoices
712
+ });
713
+ useEffect2(() => {
506
714
  if (field.input_kind !== "json") {
507
715
  return;
508
716
  }
509
717
  setJsonTextValue(stringifyJsonValue(value));
510
718
  setJsonError(null);
511
719
  }, [field.input_kind, value]);
512
- useEffect(() => {
513
- if (!field.has_choices) {
514
- setChoices([]);
515
- return;
516
- }
517
- let isMounted = true;
518
- setIsLoadingChoices(true);
519
- client.getChoices(slug, field.name, searchValue || void 0, selectedIds).then((response) => {
520
- if (!isMounted) return;
521
- setChoices((current) => mergeChoices(current, response.items));
522
- }).catch(() => {
523
- if (!isMounted) return;
524
- setChoices((current) => current);
525
- }).finally(() => {
526
- if (!isMounted) return;
527
- setIsLoadingChoices(false);
528
- });
529
- return () => {
530
- isMounted = false;
531
- };
532
- }, [client, field.has_choices, field.name, searchValue, selectedIds, slug]);
533
720
  if (field.input_kind === "boolean") {
534
- return /* @__PURE__ */ jsx2(
721
+ return /* @__PURE__ */ jsx3(
535
722
  FormControlLabel,
536
723
  {
537
- control: /* @__PURE__ */ jsx2(
724
+ control: /* @__PURE__ */ jsx3(
538
725
  Switch,
539
726
  {
540
727
  checked: Boolean(value),
@@ -547,7 +734,7 @@ var FieldEditor = memo(function FieldEditor2({
547
734
  );
548
735
  }
549
736
  if (field.input_kind === "date") {
550
- return /* @__PURE__ */ jsx2(
737
+ return /* @__PURE__ */ jsx3(
551
738
  DatePicker,
552
739
  {
553
740
  label: field.label,
@@ -569,13 +756,13 @@ var FieldEditor = memo(function FieldEditor2({
569
756
  );
570
757
  }
571
758
  if (field.input_kind === "datetime") {
572
- return /* @__PURE__ */ jsx2(
759
+ return /* @__PURE__ */ jsx3(
573
760
  DateTimePicker,
574
761
  {
575
762
  label: field.label,
576
763
  disabled: readOnly,
577
764
  value: value ? dayjs(String(value)) : null,
578
- onChange: (nextValue) => onChange(nextValue ? nextValue.toISOString() : null),
765
+ onChange: (nextValue) => onChange(nextValue ? nextValue.format("YYYY-MM-DD[T]HH:mm:ss") : null),
579
766
  slotProps: {
580
767
  popper: { disablePortal: true },
581
768
  desktopPaper: buildPickerPaperProps(),
@@ -603,7 +790,7 @@ var FieldEditor = memo(function FieldEditor2({
603
790
  });
604
791
  }
605
792
  if (field.input_kind === "json") {
606
- return /* @__PURE__ */ jsx2(
793
+ return /* @__PURE__ */ jsx3(
607
794
  TextField,
608
795
  {
609
796
  label: field.label,
@@ -636,7 +823,7 @@ var FieldEditor = memo(function FieldEditor2({
636
823
  }
637
824
  );
638
825
  }
639
- return /* @__PURE__ */ jsx2(
826
+ return /* @__PURE__ */ jsx3(
640
827
  TextField,
641
828
  {
642
829
  label: field.label,
@@ -675,7 +862,7 @@ function renderChoiceEditor({
675
862
  };
676
863
  });
677
864
  if (field.is_relation_many || field.input_kind === "relation-multiple") {
678
- return /* @__PURE__ */ jsx2(
865
+ return /* @__PURE__ */ jsx3(
679
866
  Autocomplete,
680
867
  {
681
868
  multiple: true,
@@ -701,36 +888,37 @@ function renderChoiceEditor({
701
888
  },
702
889
  listbox: { sx: { maxHeight: 240 } }
703
890
  },
704
- renderInput: (params) => {
891
+ renderInput: (params) => (() => {
705
892
  var _a2;
706
- return /* @__PURE__ */ jsx2(
893
+ const { InputProps, inputProps, ...textFieldParams } = params;
894
+ return /* @__PURE__ */ jsx3(
707
895
  TextField,
708
896
  {
709
- ...params,
897
+ ...textFieldParams,
710
898
  label: field.label,
711
899
  placeholder: searchPlaceholder,
712
900
  helperText: (_a2 = field.help_text) != null ? _a2 : void 0,
713
901
  slotProps: {
714
902
  input: {
715
- ...params.InputProps,
903
+ ...InputProps,
716
904
  endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
717
- isLoadingChoices ? /* @__PURE__ */ jsx2(CircularProgress, { color: "inherit", size: 16 }) : null,
718
- params.InputProps.endAdornment
905
+ isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
906
+ InputProps.endAdornment
719
907
  ] })
720
908
  },
721
909
  htmlInput: {
722
- ...params.inputProps,
910
+ ...inputProps,
723
911
  autoComplete: "new-password",
724
912
  name: `admin-choice-${field.name}`
725
913
  }
726
914
  }
727
915
  }
728
916
  );
729
- }
917
+ })()
730
918
  }
731
919
  );
732
920
  }
733
- return /* @__PURE__ */ jsx2(
921
+ return /* @__PURE__ */ jsx3(
734
922
  Autocomplete,
735
923
  {
736
924
  options: choices,
@@ -758,32 +946,33 @@ function renderChoiceEditor({
758
946
  },
759
947
  listbox: { sx: { maxHeight: 240 } }
760
948
  },
761
- renderInput: (params) => {
949
+ renderInput: (params) => (() => {
762
950
  var _a2;
763
- return /* @__PURE__ */ jsx2(
951
+ const { InputProps, inputProps, ...textFieldParams } = params;
952
+ return /* @__PURE__ */ jsx3(
764
953
  TextField,
765
954
  {
766
- ...params,
955
+ ...textFieldParams,
767
956
  label: field.label,
768
957
  placeholder: searchPlaceholder,
769
958
  helperText: (_a2 = field.help_text) != null ? _a2 : void 0,
770
959
  slotProps: {
771
960
  input: {
772
- ...params.InputProps,
961
+ ...InputProps,
773
962
  endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [
774
- isLoadingChoices ? /* @__PURE__ */ jsx2(CircularProgress, { color: "inherit", size: 16 }) : null,
775
- params.InputProps.endAdornment
963
+ isLoadingChoices ? /* @__PURE__ */ jsx3(CircularProgress, { color: "inherit", size: 16 }) : null,
964
+ InputProps.endAdornment
776
965
  ] })
777
966
  },
778
967
  htmlInput: {
779
- ...params.inputProps,
968
+ ...inputProps,
780
969
  autoComplete: "new-password",
781
970
  name: `admin-choice-${field.name}`
782
971
  }
783
972
  }
784
973
  }
785
974
  );
786
- }
975
+ })()
787
976
  }
788
977
  );
789
978
  }
@@ -847,7 +1036,7 @@ function stringifyJsonValue(value) {
847
1036
  }
848
1037
 
849
1038
  // src/components/FormDialog.tsx
850
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1039
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
851
1040
  function FormDialog({
852
1041
  open,
853
1042
  onClose,
@@ -860,27 +1049,43 @@ function FormDialog({
860
1049
  initialValues,
861
1050
  itemId
862
1051
  }) {
863
- const [values, setValues] = useState2({});
864
- const editableFieldNames = useMemo3(
1052
+ const t = useAdminTranslation();
1053
+ const [values, setValues] = useState3({});
1054
+ const [error, setError] = useState3(null);
1055
+ const [isSaving, setIsSaving] = useState3(false);
1056
+ const editableFieldNames = useMemo4(
865
1057
  () => mode === "create" ? meta.create_fields : meta.update_fields,
866
1058
  [meta.create_fields, meta.update_fields, mode]
867
1059
  );
868
- const editableFields = useMemo3(
1060
+ const editableFields = useMemo4(
869
1061
  () => meta.fields.filter((field) => editableFieldNames.includes(field.name)),
870
1062
  [editableFieldNames, meta.fields]
871
1063
  );
872
- useEffect2(() => {
1064
+ useEffect3(() => {
873
1065
  setValues(initialValues != null ? initialValues : {});
1066
+ setError(null);
1067
+ setIsSaving(false);
874
1068
  }, [initialValues, open]);
875
1069
  const handleSave = async () => {
1070
+ if (isSaving) {
1071
+ return;
1072
+ }
1073
+ setIsSaving(true);
1074
+ setError(null);
876
1075
  const payload = buildAdminPayload(values, editableFields);
877
- if (mode === "create") {
878
- await client.createItem(slug, payload);
879
- } else if (itemId !== void 0) {
880
- await client.patchItem(slug, itemId, payload);
1076
+ try {
1077
+ if (mode === "create") {
1078
+ await client.createItem(slug, payload);
1079
+ } else if (itemId !== void 0) {
1080
+ await client.patchItem(slug, itemId, payload);
1081
+ }
1082
+ onSuccess();
1083
+ onClose();
1084
+ } catch (reason) {
1085
+ setError(reason instanceof Error ? reason.message : t("object_save_error"));
1086
+ } finally {
1087
+ setIsSaving(false);
881
1088
  }
882
- onSuccess();
883
- onClose();
884
1089
  };
885
1090
  return /* @__PURE__ */ jsxs2(
886
1091
  Dialog,
@@ -904,23 +1109,26 @@ function FormDialog({
904
1109
  }
905
1110
  },
906
1111
  children: [
907
- /* @__PURE__ */ jsx3(DialogTitle, { children: title }),
908
- /* @__PURE__ */ jsx3(DialogContent, { children: /* @__PURE__ */ jsx3(LocalizationProvider, { dateAdapter: AdapterDayjs, children: /* @__PURE__ */ jsx3(Box, { sx: { display: "grid", gap: 2, pt: 1 }, children: editableFields.map((field) => /* @__PURE__ */ jsx3(
909
- FieldEditor,
910
- {
911
- field,
912
- value: values[field.name],
913
- slug,
914
- client,
915
- onChange: (nextValue) => {
916
- setValues((current) => ({ ...current, [field.name]: nextValue }));
917
- }
918
- },
919
- field.name
920
- )) }) }) }),
1112
+ /* @__PURE__ */ jsx4(DialogTitle, { children: title }),
1113
+ /* @__PURE__ */ jsx4(DialogContent, { children: /* @__PURE__ */ jsx4(LocalizationProvider, { dateAdapter: AdapterDayjs, adapterLocale: meta.locale, children: /* @__PURE__ */ jsxs2(Box, { sx: { display: "grid", gap: 2, pt: 1 }, children: [
1114
+ error ? /* @__PURE__ */ jsx4(Alert, { severity: "error", children: error }) : null,
1115
+ editableFields.map((field) => /* @__PURE__ */ jsx4(
1116
+ FieldEditor,
1117
+ {
1118
+ field,
1119
+ value: values[field.name],
1120
+ slug,
1121
+ client,
1122
+ onChange: (nextValue) => {
1123
+ setValues((current) => ({ ...current, [field.name]: nextValue }));
1124
+ }
1125
+ },
1126
+ field.name
1127
+ ))
1128
+ ] }) }) }),
921
1129
  /* @__PURE__ */ jsxs2(DialogActions, { children: [
922
- /* @__PURE__ */ jsx3(Button, { onClick: onClose, children: "\u041E\u0442\u043C\u0435\u043D\u0430" }),
923
- /* @__PURE__ */ jsx3(Button, { variant: "contained", onClick: handleSave, children: "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C" })
1130
+ /* @__PURE__ */ jsx4(Button, { onClick: onClose, disabled: isSaving, children: t("cancel") }),
1131
+ /* @__PURE__ */ jsx4(Button, { variant: "contained", onClick: () => void handleSave(), disabled: isSaving, children: isSaving ? t("saving") : t("save") })
924
1132
  ] })
925
1133
  ]
926
1134
  }
@@ -928,13 +1136,13 @@ function FormDialog({
928
1136
  }
929
1137
 
930
1138
  // src/components/OverviewPage.tsx
931
- import { useEffect as useEffect5, useMemo as useMemo5, useState as useState5 } from "react";
932
- import { Alert as Alert2, Box as Box5, Grid, Paper as Paper3, Skeleton as Skeleton2, Stack as Stack5 } from "@mui/material";
1139
+ import { useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
1140
+ import { Alert as Alert3, Box as Box5, Grid, Paper as Paper3, Skeleton as Skeleton2, Stack as Stack5 } from "@mui/material";
933
1141
 
934
1142
  // src/hooks/useAdminDocumentTitle.ts
935
- import { useEffect as useEffect3 } from "react";
1143
+ import { useEffect as useEffect4 } from "react";
936
1144
  function useAdminDocumentTitle(...parts) {
937
- useEffect3(() => {
1145
+ useEffect4(() => {
938
1146
  const title = parts.map((part) => part == null ? void 0 : part.trim()).filter((part) => Boolean(part)).join(" | ");
939
1147
  if (!title) {
940
1148
  return;
@@ -944,23 +1152,23 @@ function useAdminDocumentTitle(...parts) {
944
1152
  }
945
1153
 
946
1154
  // src/components/layout/AdminDataContext.tsx
947
- import { createContext as createContext2, useContext as useContext2 } from "react";
948
- import { jsx as jsx4 } from "react/jsx-runtime";
1155
+ import { createContext as createContext3, useContext as useContext3 } from "react";
1156
+ import { jsx as jsx5 } from "react/jsx-runtime";
949
1157
  var defaultAdminDataContextValue = {
950
1158
  locale: "ru",
951
1159
  models: [],
952
1160
  blocks: []
953
1161
  };
954
- var AdminDataContext = createContext2(defaultAdminDataContextValue);
1162
+ var AdminDataContext = createContext3(defaultAdminDataContextValue);
955
1163
  function AdminDataProvider({ value, children }) {
956
- return /* @__PURE__ */ jsx4(AdminDataContext.Provider, { value, children });
1164
+ return /* @__PURE__ */ jsx5(AdminDataContext.Provider, { value, children });
957
1165
  }
958
1166
  function useAdminData() {
959
- return useContext2(AdminDataContext);
1167
+ return useContext3(AdminDataContext);
960
1168
  }
961
1169
 
962
1170
  // src/components/ModelsBlocks.tsx
963
- import { memo as memo2, useMemo as useMemo4 } from "react";
1171
+ import { memo as memo2, useMemo as useMemo5 } from "react";
964
1172
  import { Box as Box3, Stack as Stack3, useMediaQuery, useTheme } from "@mui/material";
965
1173
 
966
1174
  // src/components/models-blocks/DashboardModelsBlock.tsx
@@ -977,10 +1185,10 @@ import {
977
1185
 
978
1186
  // src/components/models-blocks/BlockTitle.tsx
979
1187
  import { Stack, Typography } from "@mui/material";
980
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1188
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
981
1189
  function BlockTitle({ block, compact = false }) {
982
1190
  return /* @__PURE__ */ jsxs3(Stack, { spacing: 0.25, children: [
983
- /* @__PURE__ */ jsx5(
1191
+ /* @__PURE__ */ jsx6(
984
1192
  Typography,
985
1193
  {
986
1194
  variant: compact ? "subtitle1" : "h6",
@@ -992,7 +1200,7 @@ function BlockTitle({ block, compact = false }) {
992
1200
  children: block.title
993
1201
  }
994
1202
  ),
995
- block.description && !compact ? /* @__PURE__ */ jsx5(Typography, { color: "text.secondary", sx: { fontSize: compact ? 12 : 14, lineHeight: 1.3 }, children: block.description }) : null
1203
+ block.description && !compact ? /* @__PURE__ */ jsx6(Typography, { color: "text.secondary", sx: { fontSize: compact ? 12 : 14, lineHeight: 1.3 }, children: block.description }) : null
996
1204
  ] });
997
1205
  }
998
1206
 
@@ -1023,11 +1231,11 @@ function getBlockSurfaceSx(block, variant) {
1023
1231
  }
1024
1232
 
1025
1233
  // src/components/models-blocks/useBlockExpandedState.ts
1026
- import { useCallback, useEffect as useEffect4, useState as useState3 } from "react";
1234
+ import { useCallback as useCallback2, useEffect as useEffect5, useState as useState4 } from "react";
1027
1235
  var STORAGE_KEY_PREFIX = "xladmin.models-block.expanded";
1028
1236
  function useBlockExpandedState(blockSlug, defaultExpanded) {
1029
- const [isExpanded, setIsExpanded] = useState3(defaultExpanded);
1030
- useEffect4(() => {
1237
+ const [isExpanded, setIsExpanded] = useState4(defaultExpanded);
1238
+ useEffect5(() => {
1031
1239
  if (typeof window === "undefined") {
1032
1240
  return;
1033
1241
  }
@@ -1038,7 +1246,7 @@ function useBlockExpandedState(blockSlug, defaultExpanded) {
1038
1246
  }
1039
1247
  setIsExpanded(rawValue === "true");
1040
1248
  }, [blockSlug, defaultExpanded]);
1041
- const updateExpanded = useCallback((nextExpanded) => {
1249
+ const updateExpanded = useCallback2((nextExpanded) => {
1042
1250
  setIsExpanded(nextExpanded);
1043
1251
  if (typeof window !== "undefined") {
1044
1252
  window.localStorage.setItem(getStorageKey(blockSlug), String(nextExpanded));
@@ -1051,14 +1259,23 @@ function getStorageKey(blockSlug) {
1051
1259
  }
1052
1260
 
1053
1261
  // src/components/NavLink.tsx
1054
- import Link from "next/link.js";
1055
- import { jsx as jsx6 } from "react/jsx-runtime";
1056
- function NavLink({ href, children, style, title, onClick }) {
1057
- return /* @__PURE__ */ jsx6(Link, { href, style, title, onClick, children });
1262
+ import { jsx as jsx7 } from "react/jsx-runtime";
1263
+ function NavLink({ href, children, style, title, onClick, router }) {
1264
+ const resolvedRouter = useXLAdminRouter(router);
1265
+ return /* @__PURE__ */ jsx7(
1266
+ "a",
1267
+ {
1268
+ href,
1269
+ style,
1270
+ title,
1271
+ onClick: (event) => handleNavLinkClick(event, { href, onClick, router: resolvedRouter }),
1272
+ children
1273
+ }
1274
+ );
1058
1275
  }
1059
1276
 
1060
1277
  // src/components/models-blocks/DashboardModelsBlock.tsx
1061
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1278
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1062
1279
  function DashboardModelsBlock({ block, basePath }) {
1063
1280
  const [isExpanded, setIsExpanded] = useBlockExpandedState(block.slug, block.default_expanded);
1064
1281
  return /* @__PURE__ */ jsxs4(
@@ -1077,10 +1294,10 @@ function DashboardModelsBlock({ block, basePath }) {
1077
1294
  }
1078
1295
  },
1079
1296
  children: [
1080
- /* @__PURE__ */ jsx7(
1297
+ /* @__PURE__ */ jsx8(
1081
1298
  AccordionSummary,
1082
1299
  {
1083
- expandIcon: block.collapsible ? /* @__PURE__ */ jsx7(ExpandMoreIcon, { fontSize: "small" }) : void 0,
1300
+ expandIcon: block.collapsible ? /* @__PURE__ */ jsx8(ExpandMoreIcon, { fontSize: "small" }) : void 0,
1084
1301
  sx: {
1085
1302
  minHeight: "48px",
1086
1303
  pr: 1.75,
@@ -1102,33 +1319,41 @@ function DashboardModelsBlock({ block, basePath }) {
1102
1319
  mb: 1.05
1103
1320
  }
1104
1321
  },
1105
- children: /* @__PURE__ */ jsx7(BlockTitle, { block })
1322
+ children: /* @__PURE__ */ jsx8(BlockTitle, { block })
1106
1323
  }
1107
1324
  ),
1108
- /* @__PURE__ */ jsx7(AccordionDetails, { sx: { px: 1.35, pb: 1.35, pt: 0 }, children: /* @__PURE__ */ jsx7(Stack2, { spacing: 0.5, sx: { px: 0.4 }, children: block.models.map((model) => /* @__PURE__ */ jsx7(NavLink, { href: `${basePath}/${model.slug}`, style: { textDecoration: "none", display: "block" }, children: /* @__PURE__ */ jsx7(
1109
- ButtonBase,
1325
+ /* @__PURE__ */ jsx8(AccordionDetails, { sx: { px: 1.35, pb: 1.35, pt: 0 }, children: /* @__PURE__ */ jsx8(Stack2, { spacing: 0.5, sx: { px: 0.4 }, children: block.models.map((model) => /* @__PURE__ */ jsx8(
1326
+ NavLink,
1110
1327
  {
1111
- sx: {
1112
- width: "100%",
1113
- textAlign: "left",
1114
- display: "block",
1115
- borderRadius: "8px",
1116
- overflow: "hidden"
1117
- },
1118
- children: /* @__PURE__ */ jsx7(
1119
- ListItemButton,
1328
+ href: `${basePath}/${model.slug}`,
1329
+ style: { textDecoration: "none", display: "block" },
1330
+ children: /* @__PURE__ */ jsx8(
1331
+ ButtonBase,
1120
1332
  {
1121
1333
  sx: {
1334
+ width: "100%",
1335
+ textAlign: "left",
1336
+ display: "block",
1122
1337
  borderRadius: "8px",
1123
- minHeight: 42,
1124
- px: 1.25,
1125
- py: 0.4
1338
+ overflow: "hidden"
1126
1339
  },
1127
- children: /* @__PURE__ */ jsx7(ListItemText, { primary: model.title })
1340
+ children: /* @__PURE__ */ jsx8(
1341
+ ListItemButton,
1342
+ {
1343
+ sx: {
1344
+ borderRadius: "8px",
1345
+ minHeight: 42,
1346
+ px: 1.25,
1347
+ py: 0.4
1348
+ },
1349
+ children: /* @__PURE__ */ jsx8(ListItemText, { primary: model.title })
1350
+ }
1351
+ )
1128
1352
  }
1129
1353
  )
1130
- }
1131
- ) }, model.slug)) }) })
1354
+ },
1355
+ model.slug
1356
+ )) }) })
1132
1357
  ]
1133
1358
  }
1134
1359
  );
@@ -1146,7 +1371,7 @@ import {
1146
1371
  ListItemText as ListItemText2,
1147
1372
  Paper
1148
1373
  } from "@mui/material";
1149
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1374
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1150
1375
  function SidebarModelsBlock({ block, basePath, activeModelSlug, onModelNavigate }) {
1151
1376
  const [isExpanded, setIsExpanded] = useBlockExpandedState(block.slug, block.default_expanded);
1152
1377
  if (block.collapsible) {
@@ -1166,10 +1391,10 @@ function SidebarModelsBlock({ block, basePath, activeModelSlug, onModelNavigate
1166
1391
  }
1167
1392
  },
1168
1393
  children: [
1169
- /* @__PURE__ */ jsx8(
1394
+ /* @__PURE__ */ jsx9(
1170
1395
  AccordionSummary2,
1171
1396
  {
1172
- expandIcon: /* @__PURE__ */ jsx8(ExpandMoreIcon2, { fontSize: "small" }),
1397
+ expandIcon: /* @__PURE__ */ jsx9(ExpandMoreIcon2, { fontSize: "small" }),
1173
1398
  sx: {
1174
1399
  minHeight: "42px",
1175
1400
  pr: 1.25,
@@ -1185,10 +1410,10 @@ function SidebarModelsBlock({ block, basePath, activeModelSlug, onModelNavigate
1185
1410
  my: 0
1186
1411
  }
1187
1412
  },
1188
- children: /* @__PURE__ */ jsx8(BlockTitle, { block, compact: true })
1413
+ children: /* @__PURE__ */ jsx9(BlockTitle, { block, compact: true })
1189
1414
  }
1190
1415
  ),
1191
- /* @__PURE__ */ jsx8(AccordionDetails2, { sx: { p: 0 }, children: /* @__PURE__ */ jsx8(
1416
+ /* @__PURE__ */ jsx9(AccordionDetails2, { sx: { p: 0 }, children: /* @__PURE__ */ jsx9(
1192
1417
  SidebarModelsList,
1193
1418
  {
1194
1419
  models: block.models,
@@ -1210,8 +1435,8 @@ function SidebarModelsBlock({ block, basePath, activeModelSlug, onModelNavigate
1210
1435
  overflow: "hidden"
1211
1436
  },
1212
1437
  children: [
1213
- /* @__PURE__ */ jsx8(Box2, { sx: { px: 1.25, py: 1 }, children: /* @__PURE__ */ jsx8(BlockTitle, { block, compact: true }) }),
1214
- /* @__PURE__ */ jsx8(
1438
+ /* @__PURE__ */ jsx9(Box2, { sx: { px: 1.25, py: 1 }, children: /* @__PURE__ */ jsx9(BlockTitle, { block, compact: true }) }),
1439
+ /* @__PURE__ */ jsx9(
1215
1440
  SidebarModelsList,
1216
1441
  {
1217
1442
  models: block.models,
@@ -1225,16 +1450,16 @@ function SidebarModelsBlock({ block, basePath, activeModelSlug, onModelNavigate
1225
1450
  );
1226
1451
  }
1227
1452
  function SidebarModelsList({ models, basePath, activeModelSlug, onModelNavigate }) {
1228
- return /* @__PURE__ */ jsx8(List, { dense: true, disablePadding: true, sx: { display: "flex", flexDirection: "column", gap: 0.5, p: 1.35 }, children: models.map((model) => {
1453
+ return /* @__PURE__ */ jsx9(List, { dense: true, disablePadding: true, sx: { display: "flex", flexDirection: "column", gap: 0.5, p: 1.35 }, children: models.map((model) => {
1229
1454
  const href = `${basePath}/${model.slug}`;
1230
1455
  const isActive = activeModelSlug === model.slug;
1231
- return /* @__PURE__ */ jsx8(
1456
+ return /* @__PURE__ */ jsx9(
1232
1457
  NavLink,
1233
1458
  {
1234
1459
  href,
1235
1460
  style: { textDecoration: "none", display: "block" },
1236
1461
  onClick: () => onModelNavigate == null ? void 0 : onModelNavigate(href),
1237
- children: /* @__PURE__ */ jsx8(
1462
+ children: /* @__PURE__ */ jsx9(
1238
1463
  ListItemButton2,
1239
1464
  {
1240
1465
  selected: isActive,
@@ -1253,7 +1478,7 @@ function SidebarModelsList({ models, basePath, activeModelSlug, onModelNavigate
1253
1478
  backgroundColor: "rgba(255, 255, 255, 0.2)"
1254
1479
  }
1255
1480
  },
1256
- children: /* @__PURE__ */ jsx8(ListItemText2, { primary: model.title })
1481
+ children: /* @__PURE__ */ jsx9(ListItemText2, { primary: model.title })
1257
1482
  }
1258
1483
  )
1259
1484
  },
@@ -1263,7 +1488,7 @@ function SidebarModelsList({ models, basePath, activeModelSlug, onModelNavigate
1263
1488
  }
1264
1489
 
1265
1490
  // src/components/ModelsBlocks.tsx
1266
- import { jsx as jsx9 } from "react/jsx-runtime";
1491
+ import { jsx as jsx10 } from "react/jsx-runtime";
1267
1492
  var ModelsBlocks = memo2(function ModelsBlocks2({
1268
1493
  basePath,
1269
1494
  models,
@@ -1276,7 +1501,7 @@ var ModelsBlocks = memo2(function ModelsBlocks2({
1276
1501
  const theme = useTheme();
1277
1502
  const isMdUp = useMediaQuery(theme.breakpoints.up("md"));
1278
1503
  const isXlUp = useMediaQuery(theme.breakpoints.up("xl"));
1279
- const normalizedBlocks = useMemo4(() => [
1504
+ const normalizedBlocks = useMemo5(() => [
1280
1505
  ...blocks,
1281
1506
  {
1282
1507
  slug: "all-models",
@@ -1289,12 +1514,12 @@ var ModelsBlocks = memo2(function ModelsBlocks2({
1289
1514
  isAllModels: true
1290
1515
  }
1291
1516
  ], [blocks, models, t, variant]);
1292
- const dashboardColumns = useMemo4(() => {
1517
+ const dashboardColumns = useMemo5(() => {
1293
1518
  const columnCount = isXlUp ? 3 : isMdUp ? 2 : 1;
1294
1519
  return distributeBlocksByHeight(normalizedBlocks, columnCount);
1295
1520
  }, [isMdUp, isXlUp, normalizedBlocks]);
1296
1521
  if (variant === "sidebar") {
1297
- return /* @__PURE__ */ jsx9(Stack3, { spacing: 1, children: normalizedBlocks.map((block) => /* @__PURE__ */ jsx9(
1522
+ return /* @__PURE__ */ jsx10(Stack3, { spacing: 1, children: normalizedBlocks.map((block) => /* @__PURE__ */ jsx10(
1298
1523
  SidebarModelsBlock,
1299
1524
  {
1300
1525
  block,
@@ -1305,7 +1530,7 @@ var ModelsBlocks = memo2(function ModelsBlocks2({
1305
1530
  block.slug
1306
1531
  )) });
1307
1532
  }
1308
- return /* @__PURE__ */ jsx9(Box3, { sx: { display: "flex", gap: 2, alignItems: "flex-start" }, children: dashboardColumns.map((column, columnIndex) => /* @__PURE__ */ jsx9(Box3, { sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx9(Stack3, { spacing: 2, children: column.map((block) => /* @__PURE__ */ jsx9(DashboardModelsBlock, { block, basePath }, block.slug)) }) }, columnIndex)) });
1533
+ return /* @__PURE__ */ jsx10(Box3, { sx: { display: "flex", gap: 2, alignItems: "flex-start" }, children: dashboardColumns.map((column, columnIndex) => /* @__PURE__ */ jsx10(Box3, { sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx10(Stack3, { spacing: 2, children: column.map((block) => /* @__PURE__ */ jsx10(DashboardModelsBlock, { block, basePath }, block.slug)) }) }, columnIndex)) });
1309
1534
  });
1310
1535
  function distributeBlocksByHeight(blocks, columnCount) {
1311
1536
  const columns = Array.from({ length: columnCount }, () => []);
@@ -1327,15 +1552,15 @@ function getBlockWeight(block) {
1327
1552
  }
1328
1553
 
1329
1554
  // src/components/layout/MainHeader.tsx
1330
- import { useState as useState4 } from "react";
1555
+ import { useState as useState5 } from "react";
1331
1556
  import MenuIcon from "@mui/icons-material/Menu";
1332
1557
  import { Box as Box4 } from "@mui/material";
1333
1558
  import ExpandMoreIcon3 from "@mui/icons-material/ExpandMore";
1334
- import { Alert, Collapse, IconButton, Paper as Paper2, Skeleton, Stack as Stack4, Typography as Typography2 } from "@mui/material";
1559
+ import { Alert as Alert2, Collapse, IconButton, Paper as Paper2, Skeleton, Stack as Stack4, Typography as Typography2 } from "@mui/material";
1335
1560
 
1336
1561
  // src/components/layout/ShellContext.tsx
1337
- import { createContext as createContext3, useContext as useContext3 } from "react";
1338
- import { jsx as jsx10 } from "react/jsx-runtime";
1562
+ import { createContext as createContext4, useContext as useContext4 } from "react";
1563
+ import { jsx as jsx11 } from "react/jsx-runtime";
1339
1564
  var defaultShellContextValue = {
1340
1565
  isMobile: false,
1341
1566
  openMobileSidebar: () => {
@@ -1345,19 +1570,19 @@ var defaultShellContextValue = {
1345
1570
  startPendingNavigation: () => {
1346
1571
  }
1347
1572
  };
1348
- var ShellContext = createContext3(defaultShellContextValue);
1573
+ var ShellContext = createContext4(defaultShellContextValue);
1349
1574
  function ShellContextProvider({
1350
1575
  value,
1351
1576
  children
1352
1577
  }) {
1353
- return /* @__PURE__ */ jsx10(ShellContext.Provider, { value, children });
1578
+ return /* @__PURE__ */ jsx11(ShellContext.Provider, { value, children });
1354
1579
  }
1355
1580
  function useShellContext() {
1356
- return useContext3(ShellContext);
1581
+ return useContext4(ShellContext);
1357
1582
  }
1358
1583
 
1359
1584
  // src/components/layout/MainHeader.tsx
1360
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1585
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1361
1586
  var MAIN_HEADER_PADDING_X = 2.5;
1362
1587
  var MAIN_HEADER_PADDING_T = 1.35;
1363
1588
  var MAIN_HEADER_PADDING_B = 1.8;
@@ -1366,7 +1591,7 @@ var MAIN_HEADER_SUBTITLE_HEIGHT = 16;
1366
1591
  function MainHeader({ title, subtitle, details, error, beforeTitle, beforeSubtitle, actions }) {
1367
1592
  const t = useAdminTranslation();
1368
1593
  const { isMobile, openMobileSidebar } = useShellContext();
1369
- const [isDetailsOpen, setIsDetailsOpen] = useState4(false);
1594
+ const [isDetailsOpen, setIsDetailsOpen] = useState5(false);
1370
1595
  return /* @__PURE__ */ jsxs6(
1371
1596
  Paper2,
1372
1597
  {
@@ -1382,68 +1607,88 @@ function MainHeader({ title, subtitle, details, error, beforeTitle, beforeSubtit
1382
1607
  },
1383
1608
  children: [
1384
1609
  /* @__PURE__ */ jsxs6(Stack4, { spacing: 0.5, children: [
1385
- /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 1.5, alignItems: "center", justifyContent: "space-between", sx: { minWidth: 0 }, children: [
1386
- /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 1, alignItems: "center", sx: { minWidth: 0, flex: 1 }, children: [
1387
- isMobile ? /* @__PURE__ */ jsx11(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx11(
1388
- IconButton,
1389
- {
1390
- "aria-label": t("menu"),
1391
- onClick: openMobileSidebar,
1392
- size: "small",
1393
- sx: { ml: -0.5 },
1394
- children: /* @__PURE__ */ jsx11(MenuIcon, { fontSize: "small" })
1395
- }
1396
- ) }) : null,
1397
- beforeTitle ? /* @__PURE__ */ jsx11(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeTitle }) : null,
1398
- /* @__PURE__ */ jsx11(
1399
- Typography2,
1400
- {
1401
- variant: "h5",
1402
- sx: {
1403
- fontWeight: 600,
1404
- lineHeight: "2.125rem",
1405
- minWidth: 0
1406
- },
1407
- children: title
1408
- }
1409
- )
1410
- ] }),
1411
- actions ? /* @__PURE__ */ jsx11(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: actions }) : null
1412
- ] }),
1610
+ /* @__PURE__ */ jsxs6(
1611
+ Stack4,
1612
+ {
1613
+ direction: "row",
1614
+ spacing: 1.5,
1615
+ alignItems: "center",
1616
+ justifyContent: "space-between",
1617
+ sx: { minWidth: 0 },
1618
+ children: [
1619
+ /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 1, alignItems: "center", sx: { minWidth: 0, flex: 1 }, children: [
1620
+ isMobile ? /* @__PURE__ */ jsx12(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx12(
1621
+ IconButton,
1622
+ {
1623
+ "aria-label": t("menu"),
1624
+ onClick: openMobileSidebar,
1625
+ size: "small",
1626
+ sx: { ml: -0.5 },
1627
+ children: /* @__PURE__ */ jsx12(MenuIcon, { fontSize: "small" })
1628
+ }
1629
+ ) }) : null,
1630
+ beforeTitle ? /* @__PURE__ */ jsx12(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeTitle }) : null,
1631
+ /* @__PURE__ */ jsx12(
1632
+ Typography2,
1633
+ {
1634
+ variant: "h5",
1635
+ sx: {
1636
+ fontWeight: 600,
1637
+ lineHeight: "2.125rem",
1638
+ minWidth: 0
1639
+ },
1640
+ children: title
1641
+ }
1642
+ )
1643
+ ] }),
1644
+ actions ? /* @__PURE__ */ jsx12(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: actions }) : null
1645
+ ]
1646
+ }
1647
+ ),
1413
1648
  subtitle && !details ? /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 0.5, alignItems: "center", sx: { minWidth: 0 }, children: [
1414
- beforeSubtitle ? /* @__PURE__ */ jsx11(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeSubtitle }) : null,
1415
- /* @__PURE__ */ jsx11(Typography2, { color: "text.secondary", sx: { lineHeight: "1rem", minWidth: 0 }, children: subtitle })
1649
+ beforeSubtitle ? /* @__PURE__ */ jsx12(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeSubtitle }) : null,
1650
+ /* @__PURE__ */ jsx12(Typography2, { color: "text.secondary", sx: { lineHeight: "1rem", minWidth: 0 }, children: subtitle })
1416
1651
  ] }) : null,
1417
1652
  details ? /* @__PURE__ */ jsxs6(Stack4, { spacing: 0.5, children: [
1418
- /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 0.5, alignItems: "center", justifyContent: "space-between", sx: { minWidth: 0 }, children: [
1419
- /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 0.5, alignItems: "center", sx: { minWidth: 0, flex: 1 }, children: [
1420
- beforeSubtitle ? /* @__PURE__ */ jsx11(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeSubtitle }) : null,
1421
- /* @__PURE__ */ jsx11(Typography2, { color: "text.secondary", sx: { lineHeight: "1rem", minWidth: 0 }, children: subtitle })
1422
- ] }),
1423
- /* @__PURE__ */ jsx11(
1424
- IconButton,
1425
- {
1426
- "aria-label": isDetailsOpen ? t("hide_details") : t("show_details"),
1427
- onClick: () => setIsDetailsOpen((current) => !current),
1428
- size: "small",
1429
- sx: { mr: -0.5 },
1430
- children: /* @__PURE__ */ jsx11(
1431
- ExpandMoreIcon3,
1653
+ /* @__PURE__ */ jsxs6(
1654
+ Stack4,
1655
+ {
1656
+ direction: "row",
1657
+ spacing: 0.5,
1658
+ alignItems: "center",
1659
+ justifyContent: "space-between",
1660
+ sx: { minWidth: 0 },
1661
+ children: [
1662
+ /* @__PURE__ */ jsxs6(Stack4, { direction: "row", spacing: 0.5, alignItems: "center", sx: { minWidth: 0, flex: 1 }, children: [
1663
+ beforeSubtitle ? /* @__PURE__ */ jsx12(Box4, { sx: { display: "flex", alignItems: "center", flexShrink: 0 }, children: beforeSubtitle }) : null,
1664
+ /* @__PURE__ */ jsx12(Typography2, { color: "text.secondary", sx: { lineHeight: "1rem", minWidth: 0 }, children: subtitle })
1665
+ ] }),
1666
+ /* @__PURE__ */ jsx12(
1667
+ IconButton,
1432
1668
  {
1433
- fontSize: "small",
1434
- sx: {
1435
- transform: isDetailsOpen ? "rotate(180deg)" : "rotate(0deg)",
1436
- transition: "transform 0.2s ease"
1437
- }
1669
+ "aria-label": isDetailsOpen ? t("hide_details") : t("show_details"),
1670
+ onClick: () => setIsDetailsOpen((current) => !current),
1671
+ size: "small",
1672
+ sx: { mr: -0.5 },
1673
+ children: /* @__PURE__ */ jsx12(
1674
+ ExpandMoreIcon3,
1675
+ {
1676
+ fontSize: "small",
1677
+ sx: {
1678
+ transform: isDetailsOpen ? "rotate(180deg)" : "rotate(0deg)",
1679
+ transition: "transform 0.2s ease"
1680
+ }
1681
+ }
1682
+ )
1438
1683
  }
1439
1684
  )
1440
- }
1441
- )
1442
- ] }),
1443
- /* @__PURE__ */ jsx11(Collapse, { in: isDetailsOpen, children: /* @__PURE__ */ jsx11(Box4, { sx: { pt: 0.5 }, children: details }) })
1685
+ ]
1686
+ }
1687
+ ),
1688
+ /* @__PURE__ */ jsx12(Collapse, { in: isDetailsOpen, children: /* @__PURE__ */ jsx12(Box4, { sx: { pt: 0.5 }, children: details }) })
1444
1689
  ] }) : null
1445
1690
  ] }),
1446
- error ? /* @__PURE__ */ jsx11(Alert, { severity: "error", sx: { mt: 2 }, children: error }) : null
1691
+ error ? /* @__PURE__ */ jsx12(Alert2, { severity: "error", sx: { mt: 2 }, children: error }) : null
1447
1692
  ]
1448
1693
  }
1449
1694
  );
@@ -1452,7 +1697,7 @@ function MainHeaderSkeleton({
1452
1697
  titleWidth = 240,
1453
1698
  subtitleWidth = "40%"
1454
1699
  }) {
1455
- return /* @__PURE__ */ jsx11(
1700
+ return /* @__PURE__ */ jsx12(
1456
1701
  Paper2,
1457
1702
  {
1458
1703
  sx: {
@@ -1466,7 +1711,7 @@ function MainHeaderSkeleton({
1466
1711
  zIndex: 3
1467
1712
  },
1468
1713
  children: /* @__PURE__ */ jsxs6(Stack4, { spacing: 1, children: [
1469
- /* @__PURE__ */ jsx11(
1714
+ /* @__PURE__ */ jsx12(
1470
1715
  Skeleton,
1471
1716
  {
1472
1717
  variant: "rounded",
@@ -1475,7 +1720,7 @@ function MainHeaderSkeleton({
1475
1720
  sx: { transform: "none" }
1476
1721
  }
1477
1722
  ),
1478
- /* @__PURE__ */ jsx11(
1723
+ /* @__PURE__ */ jsx12(
1479
1724
  Skeleton,
1480
1725
  {
1481
1726
  variant: "rounded",
@@ -1490,14 +1735,14 @@ function MainHeaderSkeleton({
1490
1735
  }
1491
1736
 
1492
1737
  // src/components/OverviewPage.tsx
1493
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1738
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1494
1739
  var cachedModelsResponse = null;
1495
1740
  var inFlightModelsRequest = null;
1496
1741
  function OverviewPage({ client, basePath }) {
1497
1742
  const t = useAdminTranslation();
1498
1743
  useAdminDocumentTitle(t("admin_title"), t("overview_title"));
1499
1744
  const adminData = useAdminData();
1500
- const shellModelsResponse = useMemo5(() => {
1745
+ const shellModelsResponse = useMemo6(() => {
1501
1746
  if (adminData.models.length === 0 && adminData.blocks.length === 0) {
1502
1747
  return null;
1503
1748
  }
@@ -1507,10 +1752,10 @@ function OverviewPage({ client, basePath }) {
1507
1752
  blocks: adminData.blocks
1508
1753
  };
1509
1754
  }, [adminData.blocks, adminData.locale, adminData.models]);
1510
- const [data, setData] = useState5(shellModelsResponse != null ? shellModelsResponse : cachedModelsResponse);
1511
- const [error, setError] = useState5(null);
1512
- const [isLoading, setIsLoading] = useState5(shellModelsResponse === null && cachedModelsResponse === null);
1513
- useEffect5(() => {
1755
+ const [data, setData] = useState6(shellModelsResponse != null ? shellModelsResponse : cachedModelsResponse);
1756
+ const [error, setError] = useState6(null);
1757
+ const [isLoading, setIsLoading] = useState6(shellModelsResponse === null && cachedModelsResponse === null);
1758
+ useEffect6(() => {
1514
1759
  let isMounted = true;
1515
1760
  if (shellModelsResponse !== null) {
1516
1761
  cachedModelsResponse = shellModelsResponse;
@@ -1550,20 +1795,20 @@ function OverviewPage({ client, basePath }) {
1550
1795
  };
1551
1796
  }, [client, shellModelsResponse, t]);
1552
1797
  if (isLoading && !data) {
1553
- return /* @__PURE__ */ jsx12(OverviewPageSkeleton, {});
1798
+ return /* @__PURE__ */ jsx13(OverviewPageSkeleton, {});
1554
1799
  }
1555
1800
  if (error || !data) {
1556
- return /* @__PURE__ */ jsx12(Alert2, { severity: "error", children: error || t("models_load_error") });
1801
+ return /* @__PURE__ */ jsx13(Alert3, { severity: "error", children: error || t("models_load_error") });
1557
1802
  }
1558
1803
  return /* @__PURE__ */ jsxs7(Stack5, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
1559
- /* @__PURE__ */ jsx12(
1804
+ /* @__PURE__ */ jsx13(
1560
1805
  MainHeader,
1561
1806
  {
1562
1807
  title: t("overview_title"),
1563
1808
  subtitle: t("overview_subtitle")
1564
1809
  }
1565
1810
  ),
1566
- /* @__PURE__ */ jsx12(
1811
+ /* @__PURE__ */ jsx13(
1567
1812
  Paper3,
1568
1813
  {
1569
1814
  sx: {
@@ -1574,7 +1819,7 @@ function OverviewPage({ client, basePath }) {
1574
1819
  display: "flex",
1575
1820
  flexDirection: "column"
1576
1821
  },
1577
- children: /* @__PURE__ */ jsx12(Box5, { sx: { flex: 1, minHeight: 0, overflow: "auto", p: 2 }, children: /* @__PURE__ */ jsx12(
1822
+ children: /* @__PURE__ */ jsx13(Box5, { sx: { flex: 1, minHeight: 0, overflow: "auto", p: 2 }, children: /* @__PURE__ */ jsx13(
1578
1823
  ModelsBlocks,
1579
1824
  {
1580
1825
  models: data.items,
@@ -1589,8 +1834,8 @@ function OverviewPage({ client, basePath }) {
1589
1834
  }
1590
1835
  function OverviewPageSkeleton() {
1591
1836
  return /* @__PURE__ */ jsxs7(Stack5, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
1592
- /* @__PURE__ */ jsx12(MainHeaderSkeleton, { titleWidth: 220, subtitleWidth: "58%" }),
1593
- /* @__PURE__ */ jsx12(
1837
+ /* @__PURE__ */ jsx13(MainHeaderSkeleton, { titleWidth: 220, subtitleWidth: "58%" }),
1838
+ /* @__PURE__ */ jsx13(
1594
1839
  Paper3,
1595
1840
  {
1596
1841
  sx: {
@@ -1601,9 +1846,9 @@ function OverviewPageSkeleton() {
1601
1846
  display: "flex",
1602
1847
  flexDirection: "column"
1603
1848
  },
1604
- children: /* @__PURE__ */ jsx12(Box5, { sx: { flex: 1, minHeight: 0, overflow: "auto", p: 2 }, children: /* @__PURE__ */ jsx12(Grid, { container: true, spacing: 2, children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx12(Grid, { size: { xs: 12, md: 6, xl: 4 }, children: /* @__PURE__ */ jsx12(Paper3, { sx: { p: 2.5, borderRadius: "10px" }, children: /* @__PURE__ */ jsxs7(Stack5, { spacing: 1, children: [
1605
- /* @__PURE__ */ jsx12(Skeleton2, { variant: "text", width: "60%", height: 36 }),
1606
- /* @__PURE__ */ jsx12(Skeleton2, { variant: "text", width: "40%", height: 24 })
1849
+ children: /* @__PURE__ */ jsx13(Box5, { sx: { flex: 1, minHeight: 0, overflow: "auto", p: 2 }, children: /* @__PURE__ */ jsx13(Grid, { container: true, spacing: 2, children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx13(Grid, { size: { xs: 12, md: 6, xl: 4 }, children: /* @__PURE__ */ jsx13(Paper3, { sx: { p: 2.5, borderRadius: "10px" }, children: /* @__PURE__ */ jsxs7(Stack5, { spacing: 1, children: [
1850
+ /* @__PURE__ */ jsx13(Skeleton2, { variant: "text", width: "60%", height: 36 }),
1851
+ /* @__PURE__ */ jsx13(Skeleton2, { variant: "text", width: "40%", height: 24 })
1607
1852
  ] }) }) }, index)) }) })
1608
1853
  }
1609
1854
  )
@@ -1611,15 +1856,13 @@ function OverviewPageSkeleton() {
1611
1856
  }
1612
1857
 
1613
1858
  // src/components/ModelPage.tsx
1614
- import { usePathname, useSearchParams } from "next/navigation.js";
1615
- import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo7, useRef, useState as useState8 } from "react";
1616
1859
  import AddIcon from "@mui/icons-material/Add";
1617
1860
  import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
1618
1861
  import ChevronRightIcon from "@mui/icons-material/ChevronRight";
1619
1862
  import ExpandMoreIcon4 from "@mui/icons-material/ExpandMore";
1620
1863
  import FilterListIcon from "@mui/icons-material/FilterList";
1621
1864
  import {
1622
- Alert as Alert4,
1865
+ Alert as Alert5,
1623
1866
  Box as Box9,
1624
1867
  Button as Button4,
1625
1868
  Checkbox as Checkbox2,
@@ -1645,8 +1888,19 @@ import {
1645
1888
  import { useTheme as useTheme2 } from "@mui/material/styles";
1646
1889
 
1647
1890
  // src/components/DeletePreviewDialog.tsx
1648
- import { Alert as Alert3, Box as Box6, Button as Button2, CircularProgress as CircularProgress2, Dialog as Dialog2, DialogActions as DialogActions2, DialogContent as DialogContent2, DialogTitle as DialogTitle2, Stack as Stack6, Typography as Typography3 } from "@mui/material";
1649
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1891
+ import {
1892
+ Alert as Alert4,
1893
+ Box as Box6,
1894
+ Button as Button2,
1895
+ CircularProgress as CircularProgress2,
1896
+ Dialog as Dialog2,
1897
+ DialogActions as DialogActions2,
1898
+ DialogContent as DialogContent2,
1899
+ DialogTitle as DialogTitle2,
1900
+ Stack as Stack6,
1901
+ Typography as Typography3
1902
+ } from "@mui/material";
1903
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1650
1904
  function DeletePreviewDialog({
1651
1905
  open,
1652
1906
  title,
@@ -1680,33 +1934,47 @@ function DeletePreviewDialog({
1680
1934
  }
1681
1935
  },
1682
1936
  children: [
1683
- /* @__PURE__ */ jsx13(DialogTitle2, { children: title }),
1937
+ /* @__PURE__ */ jsx14(DialogTitle2, { children: title }),
1684
1938
  /* @__PURE__ */ jsxs8(DialogContent2, { dividers: true, children: [
1685
1939
  isLoading ? /* @__PURE__ */ jsxs8(Stack6, { spacing: 1.5, alignItems: "center", justifyContent: "center", sx: { minHeight: 180 }, children: [
1686
- /* @__PURE__ */ jsx13(CircularProgress2, { size: 28, thickness: 4.2, color: "inherit" }),
1687
- /* @__PURE__ */ jsx13(Typography3, { color: "text.secondary", children: t("delete_preview_loading") })
1940
+ /* @__PURE__ */ jsx14(CircularProgress2, { size: 28, thickness: 4.2, color: "inherit" }),
1941
+ /* @__PURE__ */ jsx14(Typography3, { color: "text.secondary", children: t("delete_preview_loading") })
1688
1942
  ] }) : null,
1689
- !isLoading && error ? /* @__PURE__ */ jsx13(Alert3, { severity: "error", children: error }) : null,
1943
+ !isLoading && error ? /* @__PURE__ */ jsx14(Alert4, { severity: "error", children: error }) : null,
1690
1944
  !isLoading && !error && preview ? /* @__PURE__ */ jsxs8(Stack6, { spacing: 1.5, children: [
1691
- /* @__PURE__ */ jsx13(Typography3, { color: "text.secondary", children: preview.can_delete ? t("delete_preview_hint") : t("delete_preview_blocked_hint") }),
1692
- !preview.can_delete ? /* @__PURE__ */ jsx13(Alert3, { severity: "warning", children: t("delete_preview_blocked_hint") }) : null,
1945
+ /* @__PURE__ */ jsx14(Typography3, { color: "text.secondary", children: preview.can_delete ? t("delete_preview_hint") : t("delete_preview_blocked_hint") }),
1946
+ !preview.can_delete ? /* @__PURE__ */ jsx14(Alert4, { severity: "warning", children: t("delete_preview_blocked_hint") }) : null,
1693
1947
  /* @__PURE__ */ jsxs8(Stack6, { spacing: 0.5, children: [
1694
- /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_roots", { count: preview.summary.roots }) }),
1695
- /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_delete", { count: preview.summary.delete }) }),
1696
- /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_protect", { count: preview.summary.protect }) }),
1697
- /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_set_null", { count: preview.summary.set_null }) }),
1698
- /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_total", { count: preview.summary.total }) })
1948
+ /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_roots", { count: preview.summary.roots }) }),
1949
+ /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_delete", { count: preview.summary.delete }) }),
1950
+ /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_protect", { count: preview.summary.protect }) }),
1951
+ /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_set_null", { count: preview.summary.set_null }) }),
1952
+ /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_total", { count: preview.summary.total }) })
1699
1953
  ] }),
1700
- preview.summary.delete === 0 && preview.summary.protect === 0 && preview.summary.set_null === 0 ? /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_empty") }) : null,
1701
- preview.roots.length > 0 ? /* @__PURE__ */ jsx13(Stack6, { spacing: 1, children: preview.roots.map((rootNode, index) => {
1954
+ preview.summary.delete === 0 && preview.summary.protect === 0 && preview.summary.set_null === 0 ? /* @__PURE__ */ jsx14(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_empty") }) : null,
1955
+ preview.roots.length > 0 ? /* @__PURE__ */ jsx14(Stack6, { spacing: 1, children: preview.roots.map((rootNode, index) => {
1702
1956
  var _a;
1703
- return /* @__PURE__ */ jsx13(DeletePreviewNode, { node: rootNode, depth: 0 }, `${(_a = rootNode.model_slug) != null ? _a : "model"}:${rootNode.id}:${index}`);
1704
- }) }) : /* @__PURE__ */ jsx13(Typography3, { variant: "body2", color: "text.secondary", children: t("delete_preview_no_selection") })
1957
+ return /* @__PURE__ */ jsx14(
1958
+ DeletePreviewNode,
1959
+ {
1960
+ node: rootNode,
1961
+ depth: 0
1962
+ },
1963
+ `${(_a = rootNode.model_slug) != null ? _a : "model"}:${rootNode.id}:${index}`
1964
+ );
1965
+ }) }) : /* @__PURE__ */ jsx14(
1966
+ Typography3,
1967
+ {
1968
+ variant: "body2",
1969
+ color: "text.secondary",
1970
+ children: t("delete_preview_no_selection")
1971
+ }
1972
+ )
1705
1973
  ] }) : null
1706
1974
  ] }),
1707
1975
  /* @__PURE__ */ jsxs8(DialogActions2, { children: [
1708
- /* @__PURE__ */ jsx13(Button2, { onClick: onClose, disabled: isSubmitting, children: t("cancel") }),
1709
- /* @__PURE__ */ jsx13(
1976
+ /* @__PURE__ */ jsx14(Button2, { onClick: onClose, disabled: isSubmitting, children: t("cancel") }),
1977
+ /* @__PURE__ */ jsx14(
1710
1978
  Button2,
1711
1979
  {
1712
1980
  color: "error",
@@ -1733,16 +2001,16 @@ function DeletePreviewNode({ node, depth }) {
1733
2001
  },
1734
2002
  children: [
1735
2003
  /* @__PURE__ */ jsxs8(Stack6, { spacing: 0.25, children: [
1736
- /* @__PURE__ */ jsx13(Typography3, { sx: { fontWeight: 600 }, children: node.label }),
2004
+ /* @__PURE__ */ jsx14(Typography3, { sx: { fontWeight: 600 }, children: node.label }),
1737
2005
  /* @__PURE__ */ jsxs8(Typography3, { variant: "body2", color: "text.secondary", children: [
1738
2006
  node.model_title,
1739
2007
  node.relation_name ? ` \u2022 ${node.relation_name}` : "",
1740
2008
  ` \u2022 ${getEffectLabel(node.effect, t)}`
1741
2009
  ] })
1742
2010
  ] }),
1743
- node.children.length > 0 ? /* @__PURE__ */ jsx13(Stack6, { spacing: 0.75, sx: { mt: 1 }, children: node.children.map((childNode, index) => {
2011
+ node.children.length > 0 ? /* @__PURE__ */ jsx14(Stack6, { spacing: 0.75, sx: { mt: 1 }, children: node.children.map((childNode, index) => {
1744
2012
  var _a;
1745
- return /* @__PURE__ */ jsx13(
2013
+ return /* @__PURE__ */ jsx14(
1746
2014
  DeletePreviewNode,
1747
2015
  {
1748
2016
  node: childNode,
@@ -1766,10 +2034,19 @@ function getEffectLabel(effect, t) {
1766
2034
  }
1767
2035
 
1768
2036
  // src/components/model-page/ListFiltersBar.tsx
1769
- import { memo as memo3, useCallback as useCallback2, useEffect as useEffect6, useMemo as useMemo6, useState as useState6 } from "react";
1770
- import { Button as Button3, CircularProgress as CircularProgress3, Divider, MenuItem, Paper as Paper4, Stack as Stack7, TextField as TextField2, Typography as Typography4 } from "@mui/material";
1771
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1772
- var inFlightFilterChoicesRequests = /* @__PURE__ */ new Map();
2037
+ import { memo as memo3, useCallback as useCallback3, useEffect as useEffect7, useMemo as useMemo7, useState as useState7 } from "react";
2038
+ import {
2039
+ Autocomplete as Autocomplete2,
2040
+ Button as Button3,
2041
+ CircularProgress as CircularProgress3,
2042
+ Divider,
2043
+ MenuItem,
2044
+ Paper as Paper4,
2045
+ Stack as Stack7,
2046
+ TextField as TextField2,
2047
+ Typography as Typography4
2048
+ } from "@mui/material";
2049
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
1773
2050
  var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1774
2051
  client,
1775
2052
  slug,
@@ -1780,7 +2057,7 @@ var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1780
2057
  debounceMs = 300
1781
2058
  }) {
1782
2059
  const t = useAdminTranslation();
1783
- const filterGroups = useMemo6(() => {
2060
+ const filterGroups = useMemo7(() => {
1784
2061
  var _a, _b;
1785
2062
  const groupedFilters = /* @__PURE__ */ new Map();
1786
2063
  for (const filter of filters) {
@@ -1799,12 +2076,12 @@ var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1799
2076
  }
1800
2077
  return Array.from(groupedFilters.values());
1801
2078
  }, [filters]);
1802
- const activeFiltersCount = useMemo6(
2079
+ const activeFiltersCount = useMemo7(
1803
2080
  () => filters.filter((filter) => Boolean(values[filter.slug])).length,
1804
2081
  [filters, values]
1805
2082
  );
1806
2083
  if (filters.length === 0) return null;
1807
- return /* @__PURE__ */ jsx14(
2084
+ return /* @__PURE__ */ jsx15(
1808
2085
  Paper4,
1809
2086
  {
1810
2087
  sx: {
@@ -1819,10 +2096,10 @@ var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1819
2096
  children: /* @__PURE__ */ jsxs9(Stack7, { spacing: 1.5, children: [
1820
2097
  /* @__PURE__ */ jsxs9(Stack7, { direction: "row", spacing: 1, alignItems: "center", justifyContent: "space-between", children: [
1821
2098
  /* @__PURE__ */ jsxs9(Stack7, { spacing: 0.25, children: [
1822
- /* @__PURE__ */ jsx14(Typography4, { sx: { fontSize: 15, fontWeight: 700 }, children: t("filters") }),
1823
- activeFiltersCount > 0 ? /* @__PURE__ */ jsx14(Typography4, { color: "text.secondary", sx: { fontSize: 12 }, children: t("active_filters", { count: activeFiltersCount }) }) : null
2099
+ /* @__PURE__ */ jsx15(Typography4, { sx: { fontSize: 15, fontWeight: 700 }, children: t("filters") }),
2100
+ activeFiltersCount > 0 ? /* @__PURE__ */ jsx15(Typography4, { color: "text.secondary", sx: { fontSize: 12 }, children: t("active_filters", { count: activeFiltersCount }) }) : null
1824
2101
  ] }),
1825
- /* @__PURE__ */ jsx14(
2102
+ /* @__PURE__ */ jsx15(
1826
2103
  Button3,
1827
2104
  {
1828
2105
  size: "small",
@@ -1833,8 +2110,8 @@ var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1833
2110
  }
1834
2111
  )
1835
2112
  ] }),
1836
- /* @__PURE__ */ jsx14(Divider, {}),
1837
- filterGroups.map((group) => /* @__PURE__ */ jsx14(
2113
+ /* @__PURE__ */ jsx15(Divider, {}),
2114
+ filterGroups.map((group) => /* @__PURE__ */ jsx15(
1838
2115
  FilterGroupSection,
1839
2116
  {
1840
2117
  client,
@@ -1852,10 +2129,10 @@ var ListFiltersSidebar = memo3(function ListFiltersSidebar2({
1852
2129
  });
1853
2130
  function FilterGroupSection({ client, slug, group, values, onChange, debounceMs }) {
1854
2131
  return /* @__PURE__ */ jsxs9(Stack7, { spacing: 1, children: [
1855
- /* @__PURE__ */ jsx14(Typography4, { sx: { fontSize: 13, fontWeight: 700, color: "text.secondary" }, children: group.title }),
1856
- /* @__PURE__ */ jsx14(Stack7, { spacing: 1, children: group.filters.map((filter) => {
2132
+ /* @__PURE__ */ jsx15(Typography4, { sx: { fontSize: 13, fontWeight: 700, color: "text.secondary" }, children: group.title }),
2133
+ /* @__PURE__ */ jsx15(Stack7, { spacing: 1, children: group.filters.map((filter) => {
1857
2134
  var _a;
1858
- return /* @__PURE__ */ jsx14(
2135
+ return /* @__PURE__ */ jsx15(
1859
2136
  ListFilterField,
1860
2137
  {
1861
2138
  client,
@@ -1871,52 +2148,44 @@ function FilterGroupSection({ client, slug, group, values, onChange, debounceMs
1871
2148
  ] });
1872
2149
  }
1873
2150
  function ListFilterField({ client, slug, filter, value, onChange, debounceMs }) {
1874
- var _a;
2151
+ var _a, _b;
1875
2152
  const t = useAdminTranslation();
1876
- const [draftValue, setDraftValue] = useState6(value);
1877
- const [options, setOptions] = useState6(filter.options);
1878
- const [isChoicesLoaded, setIsChoicesLoaded] = useState6(filter.options.length > 0);
1879
- const [isLoadingChoices, setIsLoadingChoices] = useState6(false);
1880
- useEffect6(() => {
2153
+ const [draftValue, setDraftValue] = useState7(value);
2154
+ const [searchValue, setSearchValue] = useState7("");
2155
+ const loadChoices = useCallback3(
2156
+ async (signal) => {
2157
+ const response = await client.getFilterChoices(
2158
+ slug,
2159
+ filter.slug,
2160
+ searchValue || void 0,
2161
+ value ? [value] : void 0,
2162
+ { signal }
2163
+ );
2164
+ return response.items.map((item) => ({
2165
+ value: String(item.id),
2166
+ label: item.label
2167
+ }));
2168
+ },
2169
+ [client, filter.slug, searchValue, slug, value]
2170
+ );
2171
+ const { items: options, isLoading: isLoadingChoices } = useRemoteChoices({
2172
+ enabled: filter.has_choices && filter.input_kind === "select",
2173
+ debounceMs,
2174
+ initialItems: filter.options,
2175
+ resetKey: `${slug}:${filter.slug}`,
2176
+ queryKey: `${slug}:${filter.slug}:${searchValue}:${value}`,
2177
+ load: loadChoices
2178
+ });
2179
+ useEffect7(() => {
1881
2180
  setDraftValue(value);
1882
2181
  }, [value]);
1883
- useEffect6(() => {
1884
- setOptions(filter.options);
1885
- setIsChoicesLoaded(filter.options.length > 0);
2182
+ useEffect7(() => {
2183
+ setSearchValue("");
1886
2184
  }, [filter.slug]);
1887
- useEffect6(() => {
1888
- if (filter.options.length === 0) {
1889
- return;
1890
- }
1891
- setOptions(filter.options);
1892
- setIsChoicesLoaded(true);
1893
- }, [filter.options]);
1894
- const loadChoices = useCallback2(async () => {
1895
- if (!filter.has_choices || filter.input_kind !== "select" || isChoicesLoaded) {
2185
+ useEffect7(() => {
2186
+ if (filter.input_kind !== "text") {
1896
2187
  return;
1897
2188
  }
1898
- const requestKey = `${slug}:${filter.slug}`;
1899
- const existingRequest = inFlightFilterChoicesRequests.get(requestKey);
1900
- const request = existingRequest != null ? existingRequest : client.getFilterChoices(slug, filter.slug).then((response) => response.items.map((item) => ({
1901
- value: String(item.id),
1902
- label: item.label
1903
- }))).finally(() => {
1904
- inFlightFilterChoicesRequests.delete(requestKey);
1905
- });
1906
- if (!existingRequest) {
1907
- inFlightFilterChoicesRequests.set(requestKey, request);
1908
- }
1909
- setIsLoadingChoices(true);
1910
- try {
1911
- const nextOptions = await request;
1912
- setOptions(nextOptions);
1913
- setIsChoicesLoaded(true);
1914
- } finally {
1915
- setIsLoadingChoices(false);
1916
- }
1917
- }, [client, filter.has_choices, filter.input_kind, filter.slug, isChoicesLoaded, slug]);
1918
- useEffect6(() => {
1919
- if (filter.input_kind !== "text") return;
1920
2189
  const timeoutId = window.setTimeout(() => {
1921
2190
  if (draftValue !== value) {
1922
2191
  onChange(filter.slug, draftValue);
@@ -1926,23 +2195,7 @@ function ListFilterField({ client, slug, filter, value, onChange, debounceMs })
1926
2195
  window.clearTimeout(timeoutId);
1927
2196
  };
1928
2197
  }, [debounceMs, draftValue, filter.input_kind, filter.slug, onChange, value]);
1929
- useEffect6(() => {
1930
- if (!value || !filter.has_choices || filter.input_kind !== "select" || isChoicesLoaded) {
1931
- return;
1932
- }
1933
- let isCancelled = false;
1934
- void loadChoices().then(() => {
1935
- if (isCancelled) return;
1936
- }).catch(() => {
1937
- if (isCancelled) return;
1938
- setOptions([]);
1939
- setIsChoicesLoaded(false);
1940
- });
1941
- return () => {
1942
- isCancelled = true;
1943
- };
1944
- }, [filter.has_choices, filter.input_kind, isChoicesLoaded, loadChoices, value]);
1945
- if (filter.input_kind === "boolean" || filter.input_kind === "select") {
2198
+ if (filter.input_kind === "boolean") {
1946
2199
  return /* @__PURE__ */ jsxs9(
1947
2200
  TextField2,
1948
2201
  {
@@ -1952,27 +2205,66 @@ function ListFilterField({ client, slug, filter, value, onChange, debounceMs })
1952
2205
  label: filter.label,
1953
2206
  value,
1954
2207
  onChange: (event) => onChange(filter.slug, event.target.value),
1955
- slotProps: {
1956
- select: filter.input_kind === "select" ? { onOpen: () => void loadChoices() } : void 0
1957
- },
1958
2208
  children: [
1959
- /* @__PURE__ */ jsx14(MenuItem, { value: "", children: t("all") }),
1960
- isLoadingChoices ? /* @__PURE__ */ jsx14(MenuItem, { disabled: true, value: "__loading__", children: /* @__PURE__ */ jsxs9(Stack7, { direction: "row", spacing: 1, alignItems: "center", children: [
1961
- /* @__PURE__ */ jsx14(CircularProgress3, { size: 14, thickness: 5 }),
1962
- /* @__PURE__ */ jsx14(Typography4, { variant: "body2", children: t("loading") })
1963
- ] }) }) : null,
1964
- options.map((option) => /* @__PURE__ */ jsx14(MenuItem, { value: option.value, children: option.label }, `${filter.slug}-${option.value}`))
2209
+ /* @__PURE__ */ jsx15(MenuItem, { value: "", children: t("all") }),
2210
+ filter.options.map((option) => /* @__PURE__ */ jsx15(MenuItem, { value: option.value, children: option.label }, `${filter.slug}-${option.value}`))
1965
2211
  ]
1966
2212
  }
1967
2213
  );
1968
2214
  }
1969
- return /* @__PURE__ */ jsx14(
2215
+ if (filter.input_kind === "select") {
2216
+ const selectedOption = (_a = options.find((option) => option.value === value)) != null ? _a : value ? {
2217
+ value,
2218
+ label: value
2219
+ } : null;
2220
+ return /* @__PURE__ */ jsx15(
2221
+ Autocomplete2,
2222
+ {
2223
+ options,
2224
+ value: selectedOption,
2225
+ loading: isLoadingChoices,
2226
+ filterOptions: (items) => items,
2227
+ size: "small",
2228
+ disablePortal: true,
2229
+ isOptionEqualToValue: (option, selected) => option.value === selected.value,
2230
+ getOptionLabel: (option) => option.label,
2231
+ onChange: (_, nextValue) => {
2232
+ var _a2;
2233
+ return onChange(filter.slug, (_a2 = nextValue == null ? void 0 : nextValue.value) != null ? _a2 : "");
2234
+ },
2235
+ onInputChange: (_, nextInputValue) => setSearchValue(nextInputValue),
2236
+ renderInput: (params) => (() => {
2237
+ var _a2;
2238
+ const { InputProps, inputProps, ...textFieldParams } = params;
2239
+ return /* @__PURE__ */ jsx15(
2240
+ TextField2,
2241
+ {
2242
+ ...textFieldParams,
2243
+ label: filter.label,
2244
+ placeholder: (_a2 = filter.placeholder) != null ? _a2 : t("search"),
2245
+ slotProps: {
2246
+ input: {
2247
+ ...InputProps,
2248
+ endAdornment: /* @__PURE__ */ jsxs9(Fragment2, { children: [
2249
+ isLoadingChoices ? /* @__PURE__ */ jsx15(CircularProgress3, { color: "inherit", size: 16 }) : null,
2250
+ InputProps.endAdornment
2251
+ ] })
2252
+ },
2253
+ htmlInput: inputProps
2254
+ }
2255
+ }
2256
+ );
2257
+ })()
2258
+ }
2259
+ );
2260
+ }
2261
+ return /* @__PURE__ */ jsx15(
1970
2262
  TextField2,
1971
2263
  {
1972
2264
  size: "small",
1973
2265
  fullWidth: true,
1974
2266
  label: filter.label,
1975
- placeholder: (_a = filter.placeholder) != null ? _a : void 0,
2267
+ placeholder: (_b = filter.placeholder) != null ? _b : void 0,
1976
2268
  value: draftValue,
1977
2269
  onChange: (event) => setDraftValue(event.target.value)
1978
2270
  }
@@ -1983,7 +2275,7 @@ function ListFilterField({ client, slug, filter, value, onChange, debounceMs })
1983
2275
  import { memo as memo4 } from "react";
1984
2276
  import MoreVertIcon from "@mui/icons-material/MoreVert";
1985
2277
  import { Box as Box7, Checkbox, IconButton as IconButton2, TableCell, TableRow } from "@mui/material";
1986
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
2278
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1987
2279
  var ListRow = memo4(function ListRow2({
1988
2280
  row,
1989
2281
  pkField,
@@ -2013,7 +2305,7 @@ var ListRow = memo4(function ListRow2({
2013
2305
  }
2014
2306
  },
2015
2307
  children: [
2016
- /* @__PURE__ */ jsx15(
2308
+ /* @__PURE__ */ jsx16(
2017
2309
  TableCell,
2018
2310
  {
2019
2311
  padding: "none",
@@ -2025,7 +2317,7 @@ var ListRow = memo4(function ListRow2({
2025
2317
  textAlign: "center",
2026
2318
  px: 1
2027
2319
  },
2028
- children: /* @__PURE__ */ jsx15(
2320
+ children: /* @__PURE__ */ jsx16(
2029
2321
  Checkbox,
2030
2322
  {
2031
2323
  checked: isSelected,
@@ -2050,7 +2342,7 @@ var ListRow = memo4(function ListRow2({
2050
2342
  textOverflow: "ellipsis"
2051
2343
  };
2052
2344
  if (imageUrl) {
2053
- return /* @__PURE__ */ jsx15(TableCell, { title: fullFieldValue, sx: { ...commonCellSx, py: 0.75 }, children: /* @__PURE__ */ jsx15(
2345
+ return /* @__PURE__ */ jsx16(TableCell, { title: fullFieldValue, sx: { ...commonCellSx, py: 0.75 }, children: /* @__PURE__ */ jsx16(
2054
2346
  Box7,
2055
2347
  {
2056
2348
  component: "img",
@@ -2068,7 +2360,7 @@ var ListRow = memo4(function ListRow2({
2068
2360
  ) }, fieldName);
2069
2361
  }
2070
2362
  if (index === 0) {
2071
- return /* @__PURE__ */ jsx15(
2363
+ return /* @__PURE__ */ jsx16(
2072
2364
  TableCell,
2073
2365
  {
2074
2366
  title: fullFieldValue,
@@ -2086,7 +2378,7 @@ var ListRow = memo4(function ListRow2({
2086
2378
  color: "primary.main"
2087
2379
  }
2088
2380
  },
2089
- children: /* @__PURE__ */ jsx15(
2381
+ children: /* @__PURE__ */ jsx16(
2090
2382
  NavLink,
2091
2383
  {
2092
2384
  href: `${basePath}/${slug}/${rowId}`,
@@ -2099,9 +2391,9 @@ var ListRow = memo4(function ListRow2({
2099
2391
  fieldName
2100
2392
  );
2101
2393
  }
2102
- return /* @__PURE__ */ jsx15(TableCell, { title: fullFieldValue, sx: commonCellSx, children: fieldValue }, fieldName);
2394
+ return /* @__PURE__ */ jsx16(TableCell, { title: fullFieldValue, sx: commonCellSx, children: fieldValue }, fieldName);
2103
2395
  }),
2104
- /* @__PURE__ */ jsx15(
2396
+ /* @__PURE__ */ jsx16(
2105
2397
  TableCell,
2106
2398
  {
2107
2399
  align: "right",
@@ -2110,7 +2402,7 @@ var ListRow = memo4(function ListRow2({
2110
2402
  minWidth: actionsColumnWidth,
2111
2403
  maxWidth: actionsColumnWidth
2112
2404
  },
2113
- children: /* @__PURE__ */ jsx15(IconButton2, { size: "small", onClick: (event) => onOpenMenu(event, rowId), children: /* @__PURE__ */ jsx15(MoreVertIcon, { fontSize: "small" }) })
2405
+ children: /* @__PURE__ */ jsx16(IconButton2, { size: "small", onClick: (event) => onOpenMenu(event, rowId), children: /* @__PURE__ */ jsx16(MoreVertIcon, { fontSize: "small" }) })
2114
2406
  }
2115
2407
  )
2116
2408
  ]
@@ -2119,20 +2411,20 @@ var ListRow = memo4(function ListRow2({
2119
2411
  });
2120
2412
 
2121
2413
  // src/components/model-page/SearchField.tsx
2122
- import { memo as memo5, useEffect as useEffect7, useState as useState7 } from "react";
2414
+ import { memo as memo5, useEffect as useEffect8, useState as useState8 } from "react";
2123
2415
  import { TextField as TextField3 } from "@mui/material";
2124
- import { jsx as jsx16 } from "react/jsx-runtime";
2416
+ import { jsx as jsx17 } from "react/jsx-runtime";
2125
2417
  var SearchField = memo5(function SearchField2({
2126
2418
  value,
2127
2419
  onCommit,
2128
2420
  debounceMs,
2129
2421
  placeholder
2130
2422
  }) {
2131
- const [draftValue, setDraftValue] = useState7(value);
2132
- useEffect7(() => {
2423
+ const [draftValue, setDraftValue] = useState8(value);
2424
+ useEffect8(() => {
2133
2425
  setDraftValue(value);
2134
2426
  }, [value]);
2135
- useEffect7(() => {
2427
+ useEffect8(() => {
2136
2428
  const timerId = window.setTimeout(() => {
2137
2429
  if (draftValue !== value) {
2138
2430
  onCommit(draftValue);
@@ -2142,7 +2434,7 @@ var SearchField = memo5(function SearchField2({
2142
2434
  window.clearTimeout(timerId);
2143
2435
  };
2144
2436
  }, [debounceMs, draftValue, onCommit, value]);
2145
- return /* @__PURE__ */ jsx16(
2437
+ return /* @__PURE__ */ jsx17(
2146
2438
  TextField3,
2147
2439
  {
2148
2440
  size: "small",
@@ -2160,98 +2452,157 @@ var SearchField = memo5(function SearchField2({
2160
2452
  );
2161
2453
  });
2162
2454
 
2163
- // src/components/model-page/Skeletons.tsx
2164
- import { Box as Box8, Paper as Paper5, Skeleton as Skeleton3, Stack as Stack8 } from "@mui/material";
2165
- import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2166
- function ModelPageSkeleton() {
2167
- return /* @__PURE__ */ jsxs11(Stack8, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
2168
- /* @__PURE__ */ jsx17(MainHeaderSkeleton, { titleWidth: 240, subtitleWidth: "38%" }),
2169
- /* @__PURE__ */ jsx17(Paper5, { sx: { p: 1.5, borderRadius: "10px", flexShrink: 0 }, children: /* @__PURE__ */ jsxs11(Stack8, { direction: { xs: "column", lg: "row" }, spacing: 1.5, alignItems: { lg: "center" }, children: [
2170
- /* @__PURE__ */ jsx17(Skeleton3, { variant: "rounded", width: 360, height: 40 }),
2171
- /* @__PURE__ */ jsx17(Skeleton3, { variant: "rounded", width: 128, height: 40 }),
2172
- /* @__PURE__ */ jsx17(Box8, { sx: { flex: 1 } }),
2173
- /* @__PURE__ */ jsx17(Skeleton3, { variant: "rounded", width: 110, height: 24 })
2174
- ] }) }),
2175
- /* @__PURE__ */ jsx17(Paper5, { sx: { borderRadius: "10px", flex: 1, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx17(ModelTableSkeleton, {}) })
2176
- ] });
2455
+ // src/components/model-page/useModelPageController.ts
2456
+ import { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo8, useRef as useRef3, useState as useState9 } from "react";
2457
+
2458
+ // src/cache.ts
2459
+ var MAX_CACHE_ENTRIES = 100;
2460
+ var clientCaches = /* @__PURE__ */ new WeakMap();
2461
+ function buildDetailCacheKey(slug, id) {
2462
+ return `detail:${slug}:${id}`;
2177
2463
  }
2178
- function ModelTableSkeleton() {
2179
- return /* @__PURE__ */ jsx17(Box8, { sx: { height: "100%", overflow: "auto", p: 1.5 }, children: /* @__PURE__ */ jsxs11(Stack8, { spacing: 1, children: [
2180
- /* @__PURE__ */ jsx17(Skeleton3, { variant: "rounded", width: "100%", height: 42 }),
2181
- Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx17(Skeleton3, { variant: "rounded", width: "100%", height: 48 }, index))
2182
- ] }) });
2464
+ function buildListCacheKey(slug, params) {
2465
+ const { q, sort, limit, offset, ...filters } = params;
2466
+ return `list:${slug}:${JSON.stringify({
2467
+ q: q != null ? q : null,
2468
+ sort: sort != null ? sort : null,
2469
+ limit: limit != null ? limit : 50,
2470
+ offset: offset != null ? offset : 0,
2471
+ filters: Object.fromEntries(
2472
+ Object.entries(filters).filter(([, value]) => value !== void 0 && value !== null && value !== "").sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
2473
+ )
2474
+ })}`;
2475
+ }
2476
+ function getClientCacheBucket(client) {
2477
+ const existingBucket = clientCaches.get(client);
2478
+ if (existingBucket) {
2479
+ return existingBucket;
2480
+ }
2481
+ const nextBucket = {
2482
+ detailResponseCache: /* @__PURE__ */ new Map(),
2483
+ inFlightDetailRequests: /* @__PURE__ */ new Map(),
2484
+ listResponseCache: /* @__PURE__ */ new Map(),
2485
+ inFlightListRequests: /* @__PURE__ */ new Map(),
2486
+ modelVersions: /* @__PURE__ */ new Map()
2487
+ };
2488
+ clientCaches.set(client, nextBucket);
2489
+ return nextBucket;
2490
+ }
2491
+ function getModelCacheVersion(client, slug) {
2492
+ var _a;
2493
+ return (_a = getClientCacheBucket(client).modelVersions.get(slug)) != null ? _a : 0;
2494
+ }
2495
+ function setCachedListResponse(client, key, response) {
2496
+ setBoundedMapEntry(getClientCacheBucket(client).listResponseCache, key, response);
2497
+ }
2498
+ function setCachedDetailResponse(client, key, response) {
2499
+ setBoundedMapEntry(getClientCacheBucket(client).detailResponseCache, key, response);
2500
+ }
2501
+ function invalidateModelCache(client, slug) {
2502
+ const bucket = getClientCacheBucket(client);
2503
+ bucket.modelVersions.set(slug, getModelCacheVersion(client, slug) + 1);
2504
+ for (const key of bucket.listResponseCache.keys()) {
2505
+ if (key.startsWith(`list:${slug}:`)) {
2506
+ bucket.listResponseCache.delete(key);
2507
+ }
2508
+ }
2509
+ for (const key of bucket.inFlightListRequests.keys()) {
2510
+ if (key.startsWith(`list:${slug}:`)) {
2511
+ bucket.inFlightListRequests.delete(key);
2512
+ }
2513
+ }
2514
+ for (const key of bucket.detailResponseCache.keys()) {
2515
+ if (key.startsWith(`detail:${slug}:`)) {
2516
+ bucket.detailResponseCache.delete(key);
2517
+ }
2518
+ }
2519
+ for (const key of bucket.inFlightDetailRequests.keys()) {
2520
+ if (key.startsWith(`detail:${slug}:`)) {
2521
+ bucket.inFlightDetailRequests.delete(key);
2522
+ }
2523
+ }
2524
+ }
2525
+ function setBoundedMapEntry(map, key, value) {
2526
+ if (map.has(key)) {
2527
+ map.delete(key);
2528
+ }
2529
+ map.set(key, value);
2530
+ while (map.size > MAX_CACHE_ENTRIES) {
2531
+ const oldestKey = map.keys().next().value;
2532
+ if (!oldestKey) {
2533
+ break;
2534
+ }
2535
+ map.delete(oldestKey);
2536
+ }
2183
2537
  }
2184
2538
 
2185
- // src/components/ModelPage.tsx
2186
- import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2539
+ // src/components/model-page/useModelPageController.ts
2187
2540
  var DEFAULT_PAGE_SIZE = 50;
2188
- var SEARCH_DEBOUNCE_MS = 300;
2189
- var CHECKBOX_COLUMN_WIDTH = 56;
2190
- var ACTIONS_COLUMN_WIDTH = 56;
2191
- var inFlightListRequests = /* @__PURE__ */ new Map();
2192
- var listResponseCache = /* @__PURE__ */ new Map();
2193
- function ModelPage({ client, basePath, slug }) {
2541
+ function useModelPageController({
2542
+ client,
2543
+ slug,
2544
+ pathname,
2545
+ locationSearch,
2546
+ router,
2547
+ t
2548
+ }) {
2194
2549
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2195
- const locale = useAdminLocale();
2196
- const t = useAdminTranslation();
2197
- const pathname = usePathname();
2198
- const searchParams = useSearchParams();
2550
+ const searchParams = useMemo8(() => new URLSearchParams(locationSearch), [locationSearch]);
2199
2551
  const initialQuery = (_a = searchParams.get("q")) != null ? _a : "";
2200
2552
  const initialSort = (_b = searchParams.get("sort")) != null ? _b : "";
2201
2553
  const initialPage = parsePageParam(searchParams.get("page"));
2202
2554
  const initialFilters = extractFilterParams(searchParams);
2203
- const initialCachedResponse = findCachedListResponse(
2204
- slug,
2205
- initialQuery || void 0,
2206
- initialSort || void 0,
2207
- DEFAULT_PAGE_SIZE,
2208
- (initialPage - 1) * DEFAULT_PAGE_SIZE,
2209
- initialFilters
2210
- );
2211
- const [selectedIds, setSelectedIds] = useState8([]);
2212
- const [createOpen, setCreateOpen] = useState8(false);
2213
- const [data, setData] = useState8(() => initialCachedResponse);
2214
- const [error, setError] = useState8(null);
2215
- const [isLoading, setIsLoading] = useState8(initialCachedResponse === null);
2216
- const [rowActionMenuAnchor, setRowActionMenuAnchor] = useState8(null);
2217
- const [rowActionMenuId, setRowActionMenuId] = useState8(null);
2218
- const [bulkActionMenuAnchor, setBulkActionMenuAnchor] = useState8(null);
2219
- const [appliedQuery, setAppliedQuery] = useState8(initialQuery);
2220
- const [sortValue, setSortValue] = useState8(initialSort);
2221
- const [currentPage, setCurrentPage] = useState8(initialPage);
2222
- const [pageInput, setPageInput] = useState8(String(initialPage));
2223
- const [appliedFilters, setAppliedFilters] = useState8(initialFilters);
2224
- const [deletePreviewOpen, setDeletePreviewOpen] = useState8(false);
2225
- const [deletePreview, setDeletePreview] = useState8(null);
2226
- const [deletePreviewError, setDeletePreviewError] = useState8(null);
2227
- const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState8(false);
2228
- const [isDeleteSubmitting, setIsDeleteSubmitting] = useState8(false);
2229
- const [pendingDeleteIds, setPendingDeleteIds] = useState8([]);
2230
- const [pendingDeleteMode, setPendingDeleteMode] = useState8("single");
2231
- const [filtersOpen, setFiltersOpen] = useState8(false);
2232
- const requestIdRef = useRef(0);
2233
- const dataRef = useRef(initialCachedResponse);
2234
- const pageSizeRef = useRef((_c = initialCachedResponse == null ? void 0 : initialCachedResponse.meta.page_size) != null ? _c : DEFAULT_PAGE_SIZE);
2235
- const theme = useTheme2();
2236
- const isMobile = useMediaQuery2(theme.breakpoints.down("md"));
2237
- const sortFields = useMemo7(() => sortValue.split(",").filter(Boolean), [sortValue]);
2238
- const meta = (_d = data == null ? void 0 : data.meta) != null ? _d : null;
2239
- const rows = (_e = data == null ? void 0 : data.items) != null ? _e : [];
2240
- const total = (_f = data == null ? void 0 : data.pagination.total) != null ? _f : 0;
2241
- const pageSize = (_g = meta == null ? void 0 : meta.page_size) != null ? _g : pageSizeRef.current;
2555
+ const initialCachedResponse = (_c = getClientCacheBucket(client).listResponseCache.get(
2556
+ buildListCacheKey(slug, {
2557
+ q: initialQuery || void 0,
2558
+ sort: initialSort || void 0,
2559
+ limit: DEFAULT_PAGE_SIZE,
2560
+ offset: (initialPage - 1) * DEFAULT_PAGE_SIZE,
2561
+ ...initialFilters
2562
+ })
2563
+ )) != null ? _c : null;
2564
+ const [selectedIds, setSelectedIds] = useState9([]);
2565
+ const [createOpen, setCreateOpen] = useState9(false);
2566
+ const [data, setData] = useState9(() => initialCachedResponse);
2567
+ const [error, setError] = useState9(null);
2568
+ const [isLoading, setIsLoading] = useState9(initialCachedResponse === null);
2569
+ const [rowActionMenuAnchor, setRowActionMenuAnchor] = useState9(null);
2570
+ const [rowActionMenuId, setRowActionMenuId] = useState9(null);
2571
+ const [bulkActionMenuAnchor, setBulkActionMenuAnchor] = useState9(null);
2572
+ const [appliedQuery, setAppliedQuery] = useState9(initialQuery);
2573
+ const [sortValue, setSortValue] = useState9(initialSort);
2574
+ const [currentPage, setCurrentPage] = useState9(initialPage);
2575
+ const [pageInput, setPageInput] = useState9(String(initialPage));
2576
+ const [appliedFilters, setAppliedFilters] = useState9(initialFilters);
2577
+ const [deletePreviewOpen, setDeletePreviewOpen] = useState9(false);
2578
+ const [deletePreview, setDeletePreview] = useState9(null);
2579
+ const [deletePreviewError, setDeletePreviewError] = useState9(null);
2580
+ const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState9(false);
2581
+ const [isDeleteSubmitting, setIsDeleteSubmitting] = useState9(false);
2582
+ const [pendingDeleteIds, setPendingDeleteIds] = useState9([]);
2583
+ const [pendingDeleteMode, setPendingDeleteMode] = useState9("single");
2584
+ const [filtersOpen, setFiltersOpen] = useState9(false);
2585
+ const requestIdRef = useRef3(0);
2586
+ const dataRef = useRef3(initialCachedResponse);
2587
+ const pageSizeRef = useRef3((_d = initialCachedResponse == null ? void 0 : initialCachedResponse.meta.page_size) != null ? _d : DEFAULT_PAGE_SIZE);
2588
+ const sortFields = useMemo8(() => sortValue.split(",").filter(Boolean), [sortValue]);
2589
+ const meta = (_e = data == null ? void 0 : data.meta) != null ? _e : null;
2590
+ const rows = (_f = data == null ? void 0 : data.items) != null ? _f : [];
2591
+ const total = (_g = data == null ? void 0 : data.pagination.total) != null ? _g : 0;
2592
+ const pageSize = (_h = meta == null ? void 0 : meta.page_size) != null ? _h : pageSizeRef.current;
2242
2593
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
2243
- const fieldMap = useMemo7(
2594
+ const fieldMap = useMemo8(
2244
2595
  () => {
2245
2596
  var _a2;
2246
2597
  return new Map(((_a2 = meta == null ? void 0 : meta.fields) != null ? _a2 : []).map((field) => [field.name, field]));
2247
2598
  },
2248
2599
  [meta == null ? void 0 : meta.fields]
2249
2600
  );
2250
- const listFields = (_h = meta == null ? void 0 : meta.list_fields) != null ? _h : [];
2251
- const listFilters = (_i = meta == null ? void 0 : meta.list_filters) != null ? _i : [];
2601
+ const listFields = (_i = meta == null ? void 0 : meta.list_fields) != null ? _i : [];
2602
+ const listFilters = (_j = meta == null ? void 0 : meta.list_filters) != null ? _j : [];
2252
2603
  const hasListFilters = listFilters.length > 0;
2253
- const bulkActions = (_j = meta == null ? void 0 : meta.bulk_actions) != null ? _j : [];
2254
- const selectedIdSet = useMemo7(() => new Set(selectedIds.map((item) => String(item))), [selectedIds]);
2604
+ const bulkActions = (_k = meta == null ? void 0 : meta.bulk_actions) != null ? _k : [];
2605
+ const selectedIdSet = useMemo8(() => new Set(selectedIds.map((item) => String(item))), [selectedIds]);
2255
2606
  const allVisibleSelected = rows.length > 0 && rows.every((row) => {
2256
2607
  var _a2;
2257
2608
  return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
@@ -2260,34 +2611,27 @@ function ModelPage({ client, basePath, slug }) {
2260
2611
  var _a2;
2261
2612
  return selectedIdSet.has(String(row[(_a2 = meta == null ? void 0 : meta.pk_field) != null ? _a2 : "id"]));
2262
2613
  });
2263
- useAdminDocumentTitle(t("admin_title"), (_k = meta == null ? void 0 : meta.title) != null ? _k : slug);
2264
- useEffect8(() => {
2614
+ useEffect9(() => {
2265
2615
  dataRef.current = data;
2266
2616
  }, [data]);
2267
- useEffect8(() => {
2617
+ useEffect9(() => {
2268
2618
  setPageInput(String(currentPage));
2269
2619
  }, [currentPage]);
2270
- useEffect8(() => {
2271
- const handlePopState = () => {
2272
- var _a2, _b2;
2273
- const params = new URLSearchParams(window.location.search);
2274
- const nextQuery = (_a2 = params.get("q")) != null ? _a2 : "";
2275
- const nextSort = (_b2 = params.get("sort")) != null ? _b2 : "";
2276
- const nextPage = parsePageParam(params.get("page"));
2277
- const nextFilters = extractFilterParams(params);
2278
- setAppliedQuery(nextQuery);
2279
- setSortValue(nextSort);
2280
- setCurrentPage(nextPage);
2281
- setPageInput(String(nextPage));
2282
- setAppliedFilters(nextFilters);
2283
- };
2284
- window.addEventListener("popstate", handlePopState);
2285
- return () => {
2286
- window.removeEventListener("popstate", handlePopState);
2287
- };
2288
- }, []);
2289
- const loadItems = useCallback3(async () => {
2620
+ useEffect9(() => {
2290
2621
  var _a2, _b2;
2622
+ const nextSearchParams = new URLSearchParams(locationSearch);
2623
+ const nextQuery = (_a2 = nextSearchParams.get("q")) != null ? _a2 : "";
2624
+ const nextSort = (_b2 = nextSearchParams.get("sort")) != null ? _b2 : "";
2625
+ const nextPage = parsePageParam(nextSearchParams.get("page"));
2626
+ const nextFilters = extractFilterParams(nextSearchParams);
2627
+ setAppliedQuery(nextQuery);
2628
+ setSortValue(nextSort);
2629
+ setCurrentPage(nextPage);
2630
+ setPageInput(String(nextPage));
2631
+ setAppliedFilters(nextFilters);
2632
+ }, [locationSearch]);
2633
+ const loadItems = useCallback4(async () => {
2634
+ var _a2;
2291
2635
  const activeRequestId = requestIdRef.current + 1;
2292
2636
  requestIdRef.current = activeRequestId;
2293
2637
  setError(null);
@@ -2298,14 +2642,8 @@ function ModelPage({ client, basePath, slug }) {
2298
2642
  offset: (currentPage - 1) * pageSizeRef.current,
2299
2643
  ...appliedFilters
2300
2644
  };
2301
- const cachedResponse = findCachedListResponse(
2302
- slug,
2303
- requestParams.q,
2304
- requestParams.sort,
2305
- (_a2 = requestParams.limit) != null ? _a2 : DEFAULT_PAGE_SIZE,
2306
- (_b2 = requestParams.offset) != null ? _b2 : 0,
2307
- appliedFilters
2308
- );
2645
+ const requestKey = buildListCacheKey(slug, requestParams);
2646
+ const cachedResponse = (_a2 = getClientCacheBucket(client).listResponseCache.get(requestKey)) != null ? _a2 : null;
2309
2647
  if (cachedResponse) {
2310
2648
  setData(cachedResponse);
2311
2649
  setSelectedIds([]);
@@ -2317,7 +2655,7 @@ function ModelPage({ client, basePath, slug }) {
2317
2655
  }
2318
2656
  setIsLoading(true);
2319
2657
  try {
2320
- const response = await requestListItems(client, slug, requestParams);
2658
+ const response = await requestListItems(client, slug, requestParams, requestKey);
2321
2659
  if (activeRequestId !== requestIdRef.current) {
2322
2660
  return;
2323
2661
  }
@@ -2332,32 +2670,36 @@ function ModelPage({ client, basePath, slug }) {
2332
2670
  }
2333
2671
  }
2334
2672
  }, [appliedFilters, appliedQuery, client, currentPage, slug, sortValue, t]);
2335
- useEffect8(() => {
2673
+ useEffect9(() => {
2336
2674
  void loadItems();
2337
2675
  }, [loadItems]);
2338
- const refresh = useCallback3(async () => {
2676
+ const refresh = useCallback4(async () => {
2677
+ invalidateModelCache(client, slug);
2339
2678
  await loadItems();
2340
- }, [loadItems]);
2341
- const handleSearchCommit = useCallback3((nextQuery) => {
2679
+ }, [client, loadItems, slug]);
2680
+ const replaceLocation = useCallback4((nextQuery, nextSort, nextPage, nextFilters) => {
2681
+ router.replace(buildUrlWithParams(pathname, nextQuery, nextSort, nextPage, nextFilters));
2682
+ }, [pathname, router]);
2683
+ const handleSearchCommit = useCallback4((nextQuery) => {
2342
2684
  if (nextQuery === appliedQuery) {
2343
2685
  return;
2344
2686
  }
2345
2687
  setAppliedQuery(nextQuery);
2346
2688
  setCurrentPage(1);
2347
2689
  setPageInput("1");
2348
- replaceUrlParams(pathname, nextQuery, sortValue, 1, appliedFilters);
2349
- }, [appliedFilters, appliedQuery, pathname, sortValue]);
2350
- const handlePageChange = useCallback3((nextPage) => {
2690
+ replaceLocation(nextQuery, sortValue, 1, appliedFilters);
2691
+ }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2692
+ const handlePageChange = useCallback4((nextPage) => {
2351
2693
  const safePage = Math.min(Math.max(nextPage, 1), totalPages);
2352
2694
  setCurrentPage(safePage);
2353
2695
  setPageInput(String(safePage));
2354
- replaceUrlParams(pathname, appliedQuery, sortValue, safePage, appliedFilters);
2355
- }, [appliedFilters, appliedQuery, pathname, sortValue, totalPages]);
2356
- const handlePageInputCommit = useCallback3(() => {
2696
+ replaceLocation(appliedQuery, sortValue, safePage, appliedFilters);
2697
+ }, [appliedFilters, appliedQuery, replaceLocation, sortValue, totalPages]);
2698
+ const handlePageInputCommit = useCallback4(() => {
2357
2699
  const parsedPage = parsePageParam(pageInput);
2358
2700
  handlePageChange(parsedPage);
2359
2701
  }, [handlePageChange, pageInput]);
2360
- const toggleSort = useCallback3((fieldName) => {
2702
+ const toggleSort = useCallback4((fieldName) => {
2361
2703
  const nextSortFields = sortFields.filter((item) => item !== fieldName && item !== `-${fieldName}`);
2362
2704
  const currentSort = sortFields.find((item) => item === fieldName || item === `-${fieldName}`);
2363
2705
  if (currentSort === void 0) {
@@ -2371,9 +2713,9 @@ function ModelPage({ client, basePath, slug }) {
2371
2713
  setSortValue(nextSortValue);
2372
2714
  setCurrentPage(1);
2373
2715
  setPageInput("1");
2374
- replaceUrlParams(pathname, appliedQuery, nextSortValue, 1, appliedFilters);
2375
- }, [appliedFilters, appliedQuery, pathname, sortFields]);
2376
- const handleFilterChange = useCallback3((filterSlug, value) => {
2716
+ replaceLocation(appliedQuery, nextSortValue, 1, appliedFilters);
2717
+ }, [appliedFilters, appliedQuery, replaceLocation, sortFields]);
2718
+ const handleFilterChange = useCallback4((filterSlug, value) => {
2377
2719
  const nextFilters = {
2378
2720
  ...appliedFilters,
2379
2721
  [filterSlug]: value
@@ -2384,18 +2726,18 @@ function ModelPage({ client, basePath, slug }) {
2384
2726
  setAppliedFilters(nextFilters);
2385
2727
  setCurrentPage(1);
2386
2728
  setPageInput("1");
2387
- replaceUrlParams(pathname, appliedQuery, sortValue, 1, nextFilters);
2388
- }, [appliedFilters, appliedQuery, pathname, sortValue]);
2389
- const handleResetFilters = useCallback3(() => {
2729
+ replaceLocation(appliedQuery, sortValue, 1, nextFilters);
2730
+ }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2731
+ const handleResetFilters = useCallback4(() => {
2390
2732
  if (Object.keys(appliedFilters).length === 0) {
2391
2733
  return;
2392
2734
  }
2393
2735
  setAppliedFilters({});
2394
2736
  setCurrentPage(1);
2395
2737
  setPageInput("1");
2396
- replaceUrlParams(pathname, appliedQuery, sortValue, 1, {});
2397
- }, [appliedFilters, appliedQuery, pathname, sortValue]);
2398
- const openSingleDeletePreview = useCallback3(async (rowId) => {
2738
+ replaceLocation(appliedQuery, sortValue, 1, {});
2739
+ }, [appliedFilters, appliedQuery, replaceLocation, sortValue]);
2740
+ const openSingleDeletePreview = useCallback4(async (rowId) => {
2399
2741
  setPendingDeleteIds([rowId]);
2400
2742
  setPendingDeleteMode("single");
2401
2743
  setDeletePreviewOpen(true);
@@ -2411,7 +2753,7 @@ function ModelPage({ client, basePath, slug }) {
2411
2753
  setIsDeletePreviewLoading(false);
2412
2754
  }
2413
2755
  }, [client, slug, t]);
2414
- const openBulkDeletePreview = useCallback3(async () => {
2756
+ const openBulkDeletePreview = useCallback4(async () => {
2415
2757
  if (selectedIds.length === 0) {
2416
2758
  return;
2417
2759
  }
@@ -2431,7 +2773,7 @@ function ModelPage({ client, basePath, slug }) {
2431
2773
  setBulkActionMenuAnchor(null);
2432
2774
  }
2433
2775
  }, [client, selectedIds, slug, t]);
2434
- const handleConfirmDelete = useCallback3(async () => {
2776
+ const handleConfirmDelete = useCallback4(async () => {
2435
2777
  if (pendingDeleteIds.length === 0) {
2436
2778
  return;
2437
2779
  }
@@ -2454,7 +2796,7 @@ function ModelPage({ client, basePath, slug }) {
2454
2796
  setIsDeleteSubmitting(false);
2455
2797
  }
2456
2798
  }, [client, pendingDeleteIds, pendingDeleteMode, refresh, slug, t]);
2457
- const handleRunNamedBulkAction = useCallback3(async (actionSlug) => {
2799
+ const handleRunNamedBulkAction = useCallback4(async (actionSlug) => {
2458
2800
  if (!actionSlug || selectedIds.length === 0) {
2459
2801
  return;
2460
2802
  }
@@ -2470,12 +2812,12 @@ function ModelPage({ client, basePath, slug }) {
2470
2812
  setError(reason instanceof Error ? reason.message : t("object_action_error"));
2471
2813
  }
2472
2814
  }, [client, openBulkDeletePreview, refresh, selectedIds, slug, t]);
2473
- const handleRowDelete = useCallback3(async (rowId) => {
2815
+ const handleRowDelete = useCallback4(async (rowId) => {
2474
2816
  setRowActionMenuAnchor(null);
2475
2817
  setRowActionMenuId(null);
2476
2818
  await openSingleDeletePreview(rowId);
2477
2819
  }, [openSingleDeletePreview]);
2478
- const handleToggleSelection = useCallback3((rowId, checked) => {
2820
+ const handleToggleSelection = useCallback4((rowId, checked) => {
2479
2821
  setSelectedIds((current) => {
2480
2822
  const rowKey = String(rowId);
2481
2823
  if (checked) {
@@ -2487,7 +2829,7 @@ function ModelPage({ client, basePath, slug }) {
2487
2829
  return current.filter((item) => String(item) !== rowKey);
2488
2830
  });
2489
2831
  }, []);
2490
- const handleToggleAllVisible = useCallback3((checked) => {
2832
+ const handleToggleAllVisible = useCallback4((checked) => {
2491
2833
  if (!meta) {
2492
2834
  return;
2493
2835
  }
@@ -2497,26 +2839,172 @@ function ModelPage({ client, basePath, slug }) {
2497
2839
  }
2498
2840
  setSelectedIds([]);
2499
2841
  }, [meta, rows]);
2500
- const handleOpenRowMenu = useCallback3((event, rowId) => {
2842
+ const handleOpenRowMenu = useCallback4((event, rowId) => {
2501
2843
  setRowActionMenuAnchor(event.currentTarget);
2502
2844
  setRowActionMenuId(rowId);
2503
2845
  }, []);
2504
- if (!isLoading && error && !data) {
2505
- return /* @__PURE__ */ jsx18(Alert4, { severity: "error", children: error });
2846
+ return {
2847
+ allVisibleSelected,
2848
+ appliedFilters,
2849
+ appliedQuery,
2850
+ bulkActionMenuAnchor,
2851
+ bulkActions,
2852
+ createOpen,
2853
+ currentPage,
2854
+ data,
2855
+ deletePreview,
2856
+ deletePreviewError,
2857
+ deletePreviewOpen,
2858
+ error,
2859
+ fieldMap,
2860
+ filtersOpen,
2861
+ handleConfirmDelete,
2862
+ handleFilterChange,
2863
+ handleOpenRowMenu,
2864
+ handlePageChange,
2865
+ handlePageInputCommit,
2866
+ handleResetFilters,
2867
+ handleRowDelete,
2868
+ handleRunNamedBulkAction,
2869
+ handleSearchCommit,
2870
+ handleToggleAllVisible,
2871
+ handleToggleSelection,
2872
+ hasListFilters,
2873
+ hasVisibleSelection,
2874
+ isDeletePreviewLoading,
2875
+ isDeleteSubmitting,
2876
+ isLoading,
2877
+ listFields,
2878
+ listFilters,
2879
+ meta,
2880
+ pageInput,
2881
+ pendingDeleteIds,
2882
+ pendingDeleteMode,
2883
+ refresh,
2884
+ rowActionMenuAnchor,
2885
+ rowActionMenuId,
2886
+ rows,
2887
+ selectedIdSet,
2888
+ selectedIds,
2889
+ setBulkActionMenuAnchor,
2890
+ setCreateOpen,
2891
+ setDeletePreview,
2892
+ setDeletePreviewError,
2893
+ setDeletePreviewOpen,
2894
+ setFiltersOpen,
2895
+ setPageInput,
2896
+ setPendingDeleteIds,
2897
+ setRowActionMenuAnchor,
2898
+ setRowActionMenuId,
2899
+ sortFields,
2900
+ toggleSort,
2901
+ total,
2902
+ totalPages
2903
+ };
2904
+ }
2905
+ function requestListItems(client, slug, params, requestKey) {
2906
+ const bucket = getClientCacheBucket(client);
2907
+ const cachedResponse = bucket.listResponseCache.get(requestKey);
2908
+ if (cachedResponse) {
2909
+ return Promise.resolve(cachedResponse);
2506
2910
  }
2507
- if (!data || !meta) {
2508
- return /* @__PURE__ */ jsx18(ModelPageSkeleton, {});
2911
+ const existingRequest = bucket.inFlightListRequests.get(requestKey);
2912
+ if (existingRequest) {
2913
+ return existingRequest;
2509
2914
  }
2510
- return /* @__PURE__ */ jsxs12(Stack9, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
2511
- /* @__PURE__ */ jsx18(
2915
+ const cacheVersion = getModelCacheVersion(client, slug);
2916
+ const request = client.getItems(slug, params).then((response) => {
2917
+ if (getModelCacheVersion(client, slug) === cacheVersion) {
2918
+ setCachedListResponse(client, requestKey, response);
2919
+ }
2920
+ return response;
2921
+ }).finally(() => {
2922
+ bucket.inFlightListRequests.delete(requestKey);
2923
+ });
2924
+ bucket.inFlightListRequests.set(requestKey, request);
2925
+ return request;
2926
+ }
2927
+ function parsePageParam(value) {
2928
+ const parsedValue = Number(value);
2929
+ if (!Number.isFinite(parsedValue) || parsedValue < 1) {
2930
+ return 1;
2931
+ }
2932
+ return Math.floor(parsedValue);
2933
+ }
2934
+ function extractFilterParams(params) {
2935
+ const result = {};
2936
+ for (const [key, value] of params.entries()) {
2937
+ if (key === "q" || key === "sort" || key === "page") {
2938
+ continue;
2939
+ }
2940
+ if (value) {
2941
+ result[key] = value;
2942
+ }
2943
+ }
2944
+ return result;
2945
+ }
2946
+
2947
+ // src/components/model-page/Skeletons.tsx
2948
+ import { Box as Box8, Paper as Paper5, Skeleton as Skeleton3, Stack as Stack8 } from "@mui/material";
2949
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2950
+ function ModelPageSkeleton() {
2951
+ return /* @__PURE__ */ jsxs11(Stack8, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
2952
+ /* @__PURE__ */ jsx18(MainHeaderSkeleton, { titleWidth: 240, subtitleWidth: "38%" }),
2953
+ /* @__PURE__ */ jsx18(Paper5, { sx: { p: 1.5, borderRadius: "10px", flexShrink: 0 }, children: /* @__PURE__ */ jsxs11(Stack8, { direction: { xs: "column", lg: "row" }, spacing: 1.5, alignItems: { lg: "center" }, children: [
2954
+ /* @__PURE__ */ jsx18(Skeleton3, { variant: "rounded", width: 360, height: 40 }),
2955
+ /* @__PURE__ */ jsx18(Skeleton3, { variant: "rounded", width: 128, height: 40 }),
2956
+ /* @__PURE__ */ jsx18(Box8, { sx: { flex: 1 } }),
2957
+ /* @__PURE__ */ jsx18(Skeleton3, { variant: "rounded", width: 110, height: 24 })
2958
+ ] }) }),
2959
+ /* @__PURE__ */ jsx18(Paper5, { sx: { borderRadius: "10px", flex: 1, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx18(ModelTableSkeleton, {}) })
2960
+ ] });
2961
+ }
2962
+ function ModelTableSkeleton() {
2963
+ return /* @__PURE__ */ jsx18(Box8, { sx: { height: "100%", overflow: "auto", p: 1.5 }, children: /* @__PURE__ */ jsxs11(Stack8, { spacing: 1, children: [
2964
+ /* @__PURE__ */ jsx18(Skeleton3, { variant: "rounded", width: "100%", height: 42 }),
2965
+ Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx18(Skeleton3, { variant: "rounded", width: "100%", height: 48 }, index))
2966
+ ] }) });
2967
+ }
2968
+
2969
+ // src/components/ModelPage.tsx
2970
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2971
+ var SEARCH_DEBOUNCE_MS = 300;
2972
+ var CHECKBOX_COLUMN_WIDTH = 56;
2973
+ var ACTIONS_COLUMN_WIDTH = 56;
2974
+ function ModelPage({ client, basePath, slug, router }) {
2975
+ var _a, _b;
2976
+ const locale = useAdminLocale();
2977
+ const t = useAdminTranslation();
2978
+ const resolvedRouter = useXLAdminRouter(router);
2979
+ const location = useXLAdminLocation(resolvedRouter);
2980
+ const theme = useTheme2();
2981
+ const isMobile = useMediaQuery2(theme.breakpoints.down("md"));
2982
+ const controller = useModelPageController({
2983
+ client,
2984
+ slug,
2985
+ pathname: location.pathname,
2986
+ locationSearch: location.search,
2987
+ router: resolvedRouter,
2988
+ t
2989
+ });
2990
+ useAdminDocumentTitle(t("admin_title"), (_b = (_a = controller.meta) == null ? void 0 : _a.title) != null ? _b : slug);
2991
+ if (!controller.isLoading && controller.error && !controller.data) {
2992
+ return /* @__PURE__ */ jsx19(Alert5, { severity: "error", children: controller.error });
2993
+ }
2994
+ if (!controller.data || !controller.meta) {
2995
+ return /* @__PURE__ */ jsx19(ModelPageSkeleton, {});
2996
+ }
2997
+ const meta = controller.meta;
2998
+ return /* @__PURE__ */ jsx19(XLAdminRouterProvider, { router: resolvedRouter, children: /* @__PURE__ */ jsxs12(Stack9, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
2999
+ /* @__PURE__ */ jsx19(
2512
3000
  MainHeader,
2513
3001
  {
2514
- title: meta.title,
2515
- actions: /* @__PURE__ */ jsx18(Tooltip, { title: t("create"), children: /* @__PURE__ */ jsx18(
3002
+ title: controller.meta.title,
3003
+ actions: /* @__PURE__ */ jsx19(Tooltip, { title: t("create"), children: /* @__PURE__ */ jsx19(
2516
3004
  IconButton3,
2517
3005
  {
2518
3006
  "aria-label": t("create"),
2519
- onClick: () => setCreateOpen(true),
3007
+ onClick: () => controller.setCreateOpen(true),
2520
3008
  sx: {
2521
3009
  width: 30,
2522
3010
  height: 30,
@@ -2527,14 +3015,14 @@ function ModelPage({ client, basePath, slug }) {
2527
3015
  backgroundColor: "primary.dark"
2528
3016
  }
2529
3017
  },
2530
- children: /* @__PURE__ */ jsx18(AddIcon, { fontSize: "small" })
3018
+ children: /* @__PURE__ */ jsx19(AddIcon, { fontSize: "small" })
2531
3019
  }
2532
3020
  ) }),
2533
- subtitle: `${meta.slug} \xB7 ${t("objects_count", { count: total })}`,
2534
- details: meta.description ? /* @__PURE__ */ jsx18(Typography5, { color: "text.secondary", sx: { fontSize: 14, lineHeight: 1.45 }, children: meta.description }) : void 0
3021
+ subtitle: `${controller.meta.slug} | ${t("objects_count", { count: controller.total })}`,
3022
+ details: controller.meta.description ? /* @__PURE__ */ jsx19(Typography5, { color: "text.secondary", sx: { fontSize: 14, lineHeight: 1.45 }, children: controller.meta.description }) : void 0
2535
3023
  }
2536
3024
  ),
2537
- /* @__PURE__ */ jsx18(
3025
+ /* @__PURE__ */ jsx19(
2538
3026
  Paper6,
2539
3027
  {
2540
3028
  sx: {
@@ -2543,61 +3031,61 @@ function ModelPage({ client, basePath, slug }) {
2543
3031
  flexShrink: 0
2544
3032
  },
2545
3033
  children: /* @__PURE__ */ jsxs12(Stack9, { direction: { xs: "column", lg: "row" }, spacing: 1.5, alignItems: { lg: "center" }, children: [
2546
- /* @__PURE__ */ jsx18(Stack9, { direction: { xs: "column", xl: "row" }, spacing: 1.5, sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx18(
3034
+ /* @__PURE__ */ jsx19(Stack9, { direction: { xs: "column", xl: "row" }, spacing: 1.5, sx: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx19(
2547
3035
  SearchField,
2548
3036
  {
2549
- value: appliedQuery,
2550
- onCommit: handleSearchCommit,
3037
+ value: controller.appliedQuery,
3038
+ onCommit: controller.handleSearchCommit,
2551
3039
  debounceMs: SEARCH_DEBOUNCE_MS,
2552
3040
  placeholder: t("search")
2553
3041
  }
2554
3042
  ) }),
2555
- selectedIds.length > 0 ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, sx: { flex: { lg: 1 }, minWidth: 0 }, children: [
2556
- /* @__PURE__ */ jsx18(
3043
+ controller.selectedIds.length > 0 ? /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, sx: { flex: { lg: 1 }, minWidth: 0 }, children: [
3044
+ /* @__PURE__ */ jsx19(
2557
3045
  Button4,
2558
3046
  {
2559
3047
  variant: "outlined",
2560
- endIcon: /* @__PURE__ */ jsx18(ExpandMoreIcon4, {}),
2561
- onClick: (event) => setBulkActionMenuAnchor(event.currentTarget),
3048
+ endIcon: /* @__PURE__ */ jsx19(ExpandMoreIcon4, {}),
3049
+ onClick: (event) => controller.setBulkActionMenuAnchor(event.currentTarget),
2562
3050
  children: t("actions")
2563
3051
  }
2564
3052
  ),
2565
- /* @__PURE__ */ jsx18(Typography5, { color: "text.secondary", sx: { alignSelf: "center" }, children: t("selected_count", { count: selectedIds.length }) })
3053
+ /* @__PURE__ */ jsx19(Typography5, { color: "text.secondary", sx: { alignSelf: "center" }, children: t("selected_count", { count: controller.selectedIds.length }) })
2566
3054
  ] }) : null,
2567
3055
  /* @__PURE__ */ jsxs12(Stack9, { direction: "row", spacing: 1, alignItems: "center", sx: { marginLeft: "auto" }, children: [
2568
- isMobile && hasListFilters ? /* @__PURE__ */ jsx18(Tooltip, { title: t("filters"), children: /* @__PURE__ */ jsx18(
3056
+ isMobile && controller.hasListFilters ? /* @__PURE__ */ jsx19(Tooltip, { title: t("filters"), children: /* @__PURE__ */ jsx19(
2569
3057
  IconButton3,
2570
3058
  {
2571
3059
  size: "small",
2572
3060
  "aria-label": t("filters"),
2573
- onClick: () => setFiltersOpen(true),
2574
- children: /* @__PURE__ */ jsx18(FilterListIcon, { fontSize: "small" })
3061
+ onClick: () => controller.setFiltersOpen(true),
3062
+ children: /* @__PURE__ */ jsx19(FilterListIcon, { fontSize: "small" })
2575
3063
  }
2576
3064
  ) }) : null,
2577
- /* @__PURE__ */ jsx18(
3065
+ /* @__PURE__ */ jsx19(
2578
3066
  IconButton3,
2579
3067
  {
2580
3068
  size: "small",
2581
- onClick: () => handlePageChange(currentPage - 1),
2582
- disabled: currentPage <= 1,
3069
+ onClick: () => controller.handlePageChange(controller.currentPage - 1),
3070
+ disabled: controller.currentPage <= 1,
2583
3071
  sx: { mr: "-5px" },
2584
- children: /* @__PURE__ */ jsx18(ChevronLeftIcon, { fontSize: "small" })
3072
+ children: /* @__PURE__ */ jsx19(ChevronLeftIcon, { fontSize: "small" })
2585
3073
  }
2586
3074
  ),
2587
- /* @__PURE__ */ jsx18(
3075
+ /* @__PURE__ */ jsx19(
2588
3076
  InputBase,
2589
3077
  {
2590
- value: pageInput,
2591
- onChange: (event) => setPageInput(event.target.value.replace(/[^\d]/g, "") || "1"),
2592
- onBlur: handlePageInputCommit,
3078
+ value: controller.pageInput,
3079
+ onChange: (event) => controller.setPageInput(event.target.value.replace(/[^\d]/g, "") || "1"),
3080
+ onBlur: controller.handlePageInputCommit,
2593
3081
  onKeyDown: (event) => {
2594
3082
  if (event.key === "Enter") {
2595
- handlePageInputCommit();
3083
+ controller.handlePageInputCommit();
2596
3084
  }
2597
3085
  },
2598
3086
  inputProps: { inputMode: "numeric" },
2599
3087
  sx: {
2600
- width: `${Math.max(String(totalPages).length, String(currentPage).length, 1)}ch`,
3088
+ width: `${Math.max(String(controller.totalPages).length, String(controller.currentPage).length, 1)}ch`,
2601
3089
  minWidth: "1ch",
2602
3090
  mr: 0.5,
2603
3091
  fontSize: "1rem",
@@ -2621,18 +3109,18 @@ function ModelPage({ client, basePath, slug }) {
2621
3109
  sx: { minWidth: "auto", fontSize: "1rem", lineHeight: "1rem" },
2622
3110
  children: [
2623
3111
  "/ ",
2624
- totalPages
3112
+ controller.totalPages
2625
3113
  ]
2626
3114
  }
2627
3115
  ),
2628
- /* @__PURE__ */ jsx18(
3116
+ /* @__PURE__ */ jsx19(
2629
3117
  IconButton3,
2630
3118
  {
2631
3119
  size: "small",
2632
- onClick: () => handlePageChange(currentPage + 1),
2633
- disabled: currentPage >= totalPages,
3120
+ onClick: () => controller.handlePageChange(controller.currentPage + 1),
3121
+ disabled: controller.currentPage >= controller.totalPages,
2634
3122
  sx: { ml: "-5px" },
2635
- children: /* @__PURE__ */ jsx18(ChevronRightIcon, { fontSize: "small" })
3123
+ children: /* @__PURE__ */ jsx19(ChevronRightIcon, { fontSize: "small" })
2636
3124
  }
2637
3125
  )
2638
3126
  ] })
@@ -2640,7 +3128,7 @@ function ModelPage({ client, basePath, slug }) {
2640
3128
  }
2641
3129
  ),
2642
3130
  /* @__PURE__ */ jsxs12(Stack9, { direction: { xs: "column", lg: "row" }, spacing: 1.5, sx: { flex: 1, minHeight: 0 }, children: [
2643
- /* @__PURE__ */ jsx18(
3131
+ /* @__PURE__ */ jsx19(
2644
3132
  Paper6,
2645
3133
  {
2646
3134
  sx: {
@@ -2650,9 +3138,9 @@ function ModelPage({ client, basePath, slug }) {
2650
3138
  minHeight: 0,
2651
3139
  overflow: "hidden"
2652
3140
  },
2653
- children: isLoading ? /* @__PURE__ */ jsx18(ModelTableSkeleton, {}) : /* @__PURE__ */ jsx18(Box9, { sx: { height: "100%", overflow: "auto" }, children: /* @__PURE__ */ jsxs12(Table, { stickyHeader: true, size: "small", sx: { tableLayout: "fixed" }, children: [
2654
- /* @__PURE__ */ jsx18(TableHead, { children: /* @__PURE__ */ jsxs12(TableRow2, { children: [
2655
- /* @__PURE__ */ jsx18(
3141
+ children: controller.isLoading ? /* @__PURE__ */ jsx19(ModelTableSkeleton, {}) : /* @__PURE__ */ jsx19(Box9, { sx: { height: "100%", overflow: "auto" }, children: /* @__PURE__ */ jsxs12(Table, { stickyHeader: true, size: "small", sx: { tableLayout: "fixed" }, children: [
3142
+ /* @__PURE__ */ jsx19(TableHead, { children: /* @__PURE__ */ jsxs12(TableRow2, { children: [
3143
+ /* @__PURE__ */ jsx19(
2656
3144
  TableCell2,
2657
3145
  {
2658
3146
  padding: "none",
@@ -2665,22 +3153,22 @@ function ModelPage({ client, basePath, slug }) {
2665
3153
  textAlign: "center",
2666
3154
  px: 1
2667
3155
  },
2668
- children: /* @__PURE__ */ jsx18(
3156
+ children: /* @__PURE__ */ jsx19(
2669
3157
  Checkbox2,
2670
3158
  {
2671
- checked: allVisibleSelected,
2672
- indeterminate: !allVisibleSelected && hasVisibleSelection,
2673
- onChange: (_, checked) => handleToggleAllVisible(checked)
3159
+ checked: controller.allVisibleSelected,
3160
+ indeterminate: !controller.allVisibleSelected && controller.hasVisibleSelection,
3161
+ onChange: (_, checked) => controller.handleToggleAllVisible(checked)
2674
3162
  }
2675
3163
  )
2676
3164
  }
2677
3165
  ),
2678
- listFields.map((fieldName) => {
3166
+ controller.listFields.map((fieldName) => {
2679
3167
  var _a2, _b2;
2680
- const field = fieldMap.get(fieldName);
2681
- const sortDirection = resolveSortDirection(sortFields, fieldName);
3168
+ const field = controller.fieldMap.get(fieldName);
3169
+ const sortDirection = resolveSortDirection(controller.sortFields, fieldName);
2682
3170
  const isSortable = resolveFieldSortable(field);
2683
- return /* @__PURE__ */ jsx18(
3171
+ return /* @__PURE__ */ jsx19(
2684
3172
  TableCell2,
2685
3173
  {
2686
3174
  sortDirection: sortDirection != null ? sortDirection : false,
@@ -2692,8 +3180,8 @@ function ModelPage({ client, basePath, slug }) {
2692
3180
  minWidth: getListFieldWidthPx(field),
2693
3181
  maxWidth: getListFieldWidthPx(field)
2694
3182
  },
2695
- onClick: isSortable ? () => toggleSort(fieldName) : void 0,
2696
- children: isSortable ? /* @__PURE__ */ jsx18(
3183
+ onClick: isSortable ? () => controller.toggleSort(fieldName) : void 0,
3184
+ children: isSortable ? /* @__PURE__ */ jsx19(
2697
3185
  TableSortLabel,
2698
3186
  {
2699
3187
  active: Boolean(sortDirection),
@@ -2707,7 +3195,7 @@ function ModelPage({ client, basePath, slug }) {
2707
3195
  },
2708
3196
  children: (_a2 = field == null ? void 0 : field.label) != null ? _a2 : fieldName
2709
3197
  }
2710
- ) : /* @__PURE__ */ jsx18(Typography5, { component: "span", sx: {
3198
+ ) : /* @__PURE__ */ jsx19(Typography5, { component: "span", sx: {
2711
3199
  fontSize: 14,
2712
3200
  fontWeight: 700,
2713
3201
  color: "rgba(255, 255, 255, 0.96)"
@@ -2716,7 +3204,7 @@ function ModelPage({ client, basePath, slug }) {
2716
3204
  fieldName
2717
3205
  );
2718
3206
  }),
2719
- /* @__PURE__ */ jsx18(
3207
+ /* @__PURE__ */ jsx19(
2720
3208
  TableCell2,
2721
3209
  {
2722
3210
  align: "right",
@@ -2729,21 +3217,21 @@ function ModelPage({ client, basePath, slug }) {
2729
3217
  }
2730
3218
  )
2731
3219
  ] }) }),
2732
- /* @__PURE__ */ jsx18(TableBody, { children: rows.map((row) => {
3220
+ /* @__PURE__ */ jsx19(TableBody, { children: controller.rows.map((row) => {
2733
3221
  const rowId = row[meta.pk_field];
2734
- return /* @__PURE__ */ jsx18(
3222
+ return /* @__PURE__ */ jsx19(
2735
3223
  ListRow,
2736
3224
  {
2737
3225
  row,
2738
3226
  pkField: meta.pk_field,
2739
- listFields,
3227
+ listFields: controller.listFields,
2740
3228
  basePath,
2741
3229
  slug,
2742
3230
  locale,
2743
- fieldMap,
2744
- isSelected: selectedIdSet.has(String(rowId)),
2745
- onToggleSelection: handleToggleSelection,
2746
- onOpenMenu: handleOpenRowMenu
3231
+ fieldMap: controller.fieldMap,
3232
+ isSelected: controller.selectedIdSet.has(String(rowId)),
3233
+ onToggleSelection: controller.handleToggleSelection,
3234
+ onOpenMenu: controller.handleOpenRowMenu
2747
3235
  },
2748
3236
  String(rowId)
2749
3237
  );
@@ -2751,43 +3239,50 @@ function ModelPage({ client, basePath, slug }) {
2751
3239
  ] }) })
2752
3240
  }
2753
3241
  ),
2754
- !isMobile && hasListFilters ? /* @__PURE__ */ jsx18(
3242
+ !isMobile && controller.hasListFilters ? /* @__PURE__ */ jsx19(
2755
3243
  ListFiltersSidebar,
2756
3244
  {
2757
3245
  client,
2758
3246
  slug,
2759
- filters: listFilters,
2760
- values: appliedFilters,
2761
- onChange: handleFilterChange,
2762
- onReset: handleResetFilters,
3247
+ filters: controller.listFilters,
3248
+ values: controller.appliedFilters,
3249
+ onChange: controller.handleFilterChange,
3250
+ onReset: controller.handleResetFilters,
2763
3251
  debounceMs: SEARCH_DEBOUNCE_MS
2764
3252
  }
2765
3253
  ) : null
2766
3254
  ] }),
2767
- /* @__PURE__ */ jsx18(
3255
+ /* @__PURE__ */ jsx19(
2768
3256
  Menu,
2769
3257
  {
2770
- anchorEl: bulkActionMenuAnchor,
2771
- open: Boolean(bulkActionMenuAnchor),
2772
- onClose: () => setBulkActionMenuAnchor(null),
2773
- children: bulkActions.map((action) => /* @__PURE__ */ jsx18(MenuItem2, { onClick: () => void handleRunNamedBulkAction(action.slug), children: action.label }, action.slug))
3258
+ anchorEl: controller.bulkActionMenuAnchor,
3259
+ open: Boolean(controller.bulkActionMenuAnchor),
3260
+ onClose: () => controller.setBulkActionMenuAnchor(null),
3261
+ children: controller.bulkActions.map((action) => /* @__PURE__ */ jsx19(
3262
+ MenuItem2,
3263
+ {
3264
+ onClick: () => void controller.handleRunNamedBulkAction(action.slug),
3265
+ children: action.label
3266
+ },
3267
+ action.slug
3268
+ ))
2774
3269
  }
2775
3270
  ),
2776
- /* @__PURE__ */ jsx18(
3271
+ /* @__PURE__ */ jsx19(
2777
3272
  Menu,
2778
3273
  {
2779
- anchorEl: rowActionMenuAnchor,
2780
- open: Boolean(rowActionMenuAnchor),
3274
+ anchorEl: controller.rowActionMenuAnchor,
3275
+ open: Boolean(controller.rowActionMenuAnchor),
2781
3276
  onClose: () => {
2782
- setRowActionMenuAnchor(null);
2783
- setRowActionMenuId(null);
3277
+ controller.setRowActionMenuAnchor(null);
3278
+ controller.setRowActionMenuId(null);
2784
3279
  },
2785
- children: /* @__PURE__ */ jsx18(
3280
+ children: /* @__PURE__ */ jsx19(
2786
3281
  MenuItem2,
2787
3282
  {
2788
3283
  onClick: () => {
2789
- if (rowActionMenuId !== null) {
2790
- void handleRowDelete(rowActionMenuId);
3284
+ if (controller.rowActionMenuId !== null) {
3285
+ void controller.handleRowDelete(controller.rowActionMenuId);
2791
3286
  }
2792
3287
  },
2793
3288
  children: t("delete")
@@ -2795,12 +3290,12 @@ function ModelPage({ client, basePath, slug }) {
2795
3290
  )
2796
3291
  }
2797
3292
  ),
2798
- /* @__PURE__ */ jsx18(
3293
+ /* @__PURE__ */ jsx19(
2799
3294
  FormDialog,
2800
3295
  {
2801
- open: createOpen,
2802
- onClose: () => setCreateOpen(false),
2803
- onSuccess: () => void refresh(),
3296
+ open: controller.createOpen,
3297
+ onClose: () => controller.setCreateOpen(false),
3298
+ onSuccess: () => void controller.refresh(),
2804
3299
  title: `${t("create")}: ${meta.title}`,
2805
3300
  slug,
2806
3301
  mode: "create",
@@ -2811,8 +3306,8 @@ function ModelPage({ client, basePath, slug }) {
2811
3306
  /* @__PURE__ */ jsxs12(
2812
3307
  Dialog3,
2813
3308
  {
2814
- open: filtersOpen,
2815
- onClose: () => setFiltersOpen(false),
3309
+ open: controller.filtersOpen,
3310
+ onClose: () => controller.setFiltersOpen(false),
2816
3311
  fullWidth: true,
2817
3312
  maxWidth: "xs",
2818
3313
  slotProps: {
@@ -2829,44 +3324,44 @@ function ModelPage({ client, basePath, slug }) {
2829
3324
  }
2830
3325
  },
2831
3326
  children: [
2832
- /* @__PURE__ */ jsx18(DialogTitle3, { children: t("filters") }),
2833
- /* @__PURE__ */ jsx18(DialogContent3, { sx: { px: 2, pb: 2 }, children: hasListFilters ? /* @__PURE__ */ jsx18(
3327
+ /* @__PURE__ */ jsx19(DialogTitle3, { children: t("filters") }),
3328
+ /* @__PURE__ */ jsx19(DialogContent3, { sx: { px: 2, pb: 2 }, children: controller.hasListFilters ? /* @__PURE__ */ jsx19(
2834
3329
  ListFiltersSidebar,
2835
3330
  {
2836
3331
  client,
2837
3332
  slug,
2838
- filters: listFilters,
2839
- values: appliedFilters,
2840
- onChange: handleFilterChange,
2841
- onReset: handleResetFilters,
3333
+ filters: controller.listFilters,
3334
+ values: controller.appliedFilters,
3335
+ onChange: controller.handleFilterChange,
3336
+ onReset: controller.handleResetFilters,
2842
3337
  debounceMs: SEARCH_DEBOUNCE_MS
2843
3338
  }
2844
3339
  ) : null })
2845
3340
  ]
2846
3341
  }
2847
3342
  ),
2848
- /* @__PURE__ */ jsx18(
3343
+ /* @__PURE__ */ jsx19(
2849
3344
  DeletePreviewDialog,
2850
3345
  {
2851
- open: deletePreviewOpen,
2852
- title: pendingDeleteMode === "single" ? t("delete_object_title") : t("delete_bulk_title"),
2853
- preview: deletePreview,
2854
- error: deletePreviewError,
2855
- isLoading: isDeletePreviewLoading,
2856
- isSubmitting: isDeleteSubmitting,
3346
+ open: controller.deletePreviewOpen,
3347
+ title: controller.pendingDeleteMode === "single" ? t("delete_object_title") : t("delete_bulk_title"),
3348
+ preview: controller.deletePreview,
3349
+ error: controller.deletePreviewError,
3350
+ isLoading: controller.isDeletePreviewLoading,
3351
+ isSubmitting: controller.isDeleteSubmitting,
2857
3352
  onClose: () => {
2858
- if (isDeleteSubmitting) {
3353
+ if (controller.isDeleteSubmitting) {
2859
3354
  return;
2860
3355
  }
2861
- setDeletePreviewOpen(false);
2862
- setDeletePreview(null);
2863
- setDeletePreviewError(null);
2864
- setPendingDeleteIds([]);
3356
+ controller.setDeletePreviewOpen(false);
3357
+ controller.setDeletePreview(null);
3358
+ controller.setDeletePreviewError(null);
3359
+ controller.setPendingDeleteIds([]);
2865
3360
  },
2866
- onConfirm: () => void handleConfirmDelete()
3361
+ onConfirm: () => void controller.handleConfirmDelete()
2867
3362
  }
2868
3363
  )
2869
- ] });
3364
+ ] }) });
2870
3365
  }
2871
3366
  function resolveSortDirection(sortFields, fieldName) {
2872
3367
  if (sortFields.includes(`-${fieldName}`)) return "desc";
@@ -2879,89 +3374,12 @@ function resolveFieldSortable(field) {
2879
3374
  }
2880
3375
  return field.is_sortable && !field.is_relation_many && field.input_kind !== "relation-multiple" && !field.is_virtual;
2881
3376
  }
2882
- function requestListItems(client, slug, params) {
2883
- const requestKey = buildListRequestKey(slug, params);
2884
- const cachedResponse = listResponseCache.get(requestKey);
2885
- if (cachedResponse) {
2886
- return Promise.resolve(cachedResponse);
2887
- }
2888
- const existingRequest = inFlightListRequests.get(requestKey);
2889
- if (existingRequest) {
2890
- return existingRequest;
2891
- }
2892
- const request = client.getItems(slug, params).then((response) => {
2893
- listResponseCache.set(requestKey, response);
2894
- return response;
2895
- }).finally(() => {
2896
- inFlightListRequests.delete(requestKey);
2897
- });
2898
- inFlightListRequests.set(requestKey, request);
2899
- return request;
2900
- }
2901
- function buildListRequestKey(slug, params) {
2902
- const { q, sort, limit, offset, ...filters } = params;
2903
- return JSON.stringify({
2904
- slug,
2905
- q: q != null ? q : null,
2906
- sort: sort != null ? sort : null,
2907
- limit: limit != null ? limit : DEFAULT_PAGE_SIZE,
2908
- offset: offset != null ? offset : 0,
2909
- filters: Object.fromEntries(
2910
- Object.entries(filters).filter(([, value]) => value !== void 0 && value !== null && value !== "").sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
2911
- )
2912
- });
2913
- }
2914
- function findCachedListResponse(slug, query, sort, limit, offset, filters) {
2915
- var _a;
2916
- return (_a = listResponseCache.get(buildListRequestKey(slug, { q: query, sort, limit, offset, ...filters }))) != null ? _a : null;
2917
- }
2918
- function parsePageParam(value) {
2919
- const parsedValue = Number(value);
2920
- if (!Number.isFinite(parsedValue) || parsedValue < 1) {
2921
- return 1;
2922
- }
2923
- return Math.floor(parsedValue);
2924
- }
2925
- function extractFilterParams(params) {
2926
- const result = {};
2927
- for (const [key, value] of params.entries()) {
2928
- if (key === "q" || key === "sort" || key === "page") {
2929
- continue;
2930
- }
2931
- if (value) {
2932
- result[key] = value;
2933
- }
2934
- }
2935
- return result;
2936
- }
2937
- function replaceUrlParams(pathname, query, sort, page, filters) {
2938
- const params = new URLSearchParams();
2939
- if (query) {
2940
- params.set("q", query);
2941
- }
2942
- if (sort) {
2943
- params.set("sort", sort);
2944
- }
2945
- if (page > 1) {
2946
- params.set("page", String(page));
2947
- }
2948
- for (const [key, value] of Object.entries(filters)) {
2949
- if (value) {
2950
- params.set(key, value);
2951
- }
2952
- }
2953
- const nextPath = pathname != null ? pathname : window.location.pathname;
2954
- const nextUrl = params.toString() ? `${nextPath}?${params.toString()}` : nextPath;
2955
- window.history.replaceState(window.history.state, "", nextUrl);
2956
- }
2957
3377
 
2958
3378
  // src/components/ObjectPage.tsx
2959
- import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo8, useState as useState9 } from "react";
2960
- import { usePathname as usePathname2, useRouter } from "next/navigation.js";
2961
3379
  import ArrowBackIcon from "@mui/icons-material/ArrowBack";
2962
3380
  import MoreVertIcon2 from "@mui/icons-material/MoreVert";
2963
3381
  import {
2964
- Alert as Alert5,
3382
+ Alert as Alert6,
2965
3383
  Box as Box12,
2966
3384
  Button as Button5,
2967
3385
  IconButton as IconButton4,
@@ -2979,8 +3397,8 @@ import "dayjs/locale/en.js";
2979
3397
  import "dayjs/locale/ru.js";
2980
3398
 
2981
3399
  // src/components/object-page/ObjectField.tsx
2982
- import { memo as memo6, useCallback as useCallback4 } from "react";
2983
- import { jsx as jsx19 } from "react/jsx-runtime";
3400
+ import { memo as memo6, useCallback as useCallback5 } from "react";
3401
+ import { jsx as jsx20 } from "react/jsx-runtime";
2984
3402
  var ObjectField = memo6(function ObjectField2({
2985
3403
  field,
2986
3404
  value,
@@ -2988,10 +3406,10 @@ var ObjectField = memo6(function ObjectField2({
2988
3406
  client,
2989
3407
  onFieldChange
2990
3408
  }) {
2991
- const handleChange = useCallback4((nextValue) => {
3409
+ const handleChange = useCallback5((nextValue) => {
2992
3410
  onFieldChange(field.name, nextValue);
2993
3411
  }, [field.name, onFieldChange]);
2994
- return /* @__PURE__ */ jsx19(
3412
+ return /* @__PURE__ */ jsx20(
2995
3413
  FieldEditor,
2996
3414
  {
2997
3415
  field,
@@ -3005,37 +3423,45 @@ var ObjectField = memo6(function ObjectField2({
3005
3423
 
3006
3424
  // src/components/object-page/ObjectPageSkeleton.tsx
3007
3425
  import { Box as Box10, Paper as Paper7, Skeleton as Skeleton4, Stack as Stack10 } from "@mui/material";
3008
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
3426
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
3009
3427
  function ObjectPageSkeleton() {
3010
3428
  return /* @__PURE__ */ jsxs13(Stack10, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
3011
- /* @__PURE__ */ jsx20(MainHeaderSkeleton, { titleWidth: 420, subtitleWidth: "32%" }),
3012
- /* @__PURE__ */ jsxs13(Stack10, { direction: { xs: "column", lg: "row" }, spacing: 1.5, sx: { flex: 1, minHeight: 0, alignItems: "stretch" }, children: [
3013
- /* @__PURE__ */ jsx20(Paper7, { sx: { borderRadius: "10px", flex: 1, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx20(Box10, { sx: { height: "100%", overflow: "auto", p: 2.5 }, children: /* @__PURE__ */ jsx20(Stack10, { spacing: 1.5, children: Array.from({ length: 9 }).map((_, index) => /* @__PURE__ */ jsx20(Skeleton4, { variant: "rounded", width: "100%", height: 56 }, index)) }) }) }),
3014
- /* @__PURE__ */ jsx20(
3015
- Paper7,
3016
- {
3017
- sx: {
3018
- width: { xs: "100%", lg: 280 },
3019
- flexShrink: 0,
3020
- borderRadius: "10px",
3021
- p: 1.5,
3022
- alignSelf: "flex-start"
3023
- },
3024
- children: /* @__PURE__ */ jsxs13(Stack10, { spacing: 1, children: [
3025
- /* @__PURE__ */ jsx20(Skeleton4, { variant: "text", width: 90, height: 28 }),
3026
- /* @__PURE__ */ jsx20(Skeleton4, { variant: "rounded", width: "100%", height: 40 }),
3027
- /* @__PURE__ */ jsx20(Skeleton4, { variant: "rounded", width: "100%", height: 40 })
3028
- ] })
3029
- }
3030
- )
3031
- ] })
3429
+ /* @__PURE__ */ jsx21(MainHeaderSkeleton, { titleWidth: 420, subtitleWidth: "32%" }),
3430
+ /* @__PURE__ */ jsxs13(
3431
+ Stack10,
3432
+ {
3433
+ direction: { xs: "column", lg: "row" },
3434
+ spacing: 1.5,
3435
+ sx: { flex: 1, minHeight: 0, alignItems: "stretch" },
3436
+ children: [
3437
+ /* @__PURE__ */ jsx21(Paper7, { sx: { borderRadius: "10px", flex: 1, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx21(Box10, { sx: { height: "100%", overflow: "auto", p: 2.5 }, children: /* @__PURE__ */ jsx21(Stack10, { spacing: 1.5, children: Array.from({ length: 9 }).map((_, index) => /* @__PURE__ */ jsx21(Skeleton4, { variant: "rounded", width: "100%", height: 56 }, index)) }) }) }),
3438
+ /* @__PURE__ */ jsx21(
3439
+ Paper7,
3440
+ {
3441
+ sx: {
3442
+ width: { xs: "100%", lg: 280 },
3443
+ flexShrink: 0,
3444
+ borderRadius: "10px",
3445
+ p: 1.5,
3446
+ alignSelf: "flex-start"
3447
+ },
3448
+ children: /* @__PURE__ */ jsxs13(Stack10, { spacing: 1, children: [
3449
+ /* @__PURE__ */ jsx21(Skeleton4, { variant: "text", width: 90, height: 28 }),
3450
+ /* @__PURE__ */ jsx21(Skeleton4, { variant: "rounded", width: "100%", height: 40 }),
3451
+ /* @__PURE__ */ jsx21(Skeleton4, { variant: "rounded", width: "100%", height: 40 })
3452
+ ] })
3453
+ }
3454
+ )
3455
+ ]
3456
+ }
3457
+ )
3032
3458
  ] });
3033
3459
  }
3034
3460
 
3035
3461
  // src/components/object-page/ReadonlyObjectField.tsx
3036
3462
  import { memo as memo7 } from "react";
3037
3463
  import { Box as Box11, Stack as Stack11, TextField as TextField4 } from "@mui/material";
3038
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
3464
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
3039
3465
  var ReadonlyObjectField = memo7(function ReadonlyObjectField2({
3040
3466
  field,
3041
3467
  value,
@@ -3044,7 +3470,7 @@ var ReadonlyObjectField = memo7(function ReadonlyObjectField2({
3044
3470
  var _a;
3045
3471
  const isMultiline = field.input_kind === "textarea" || field.input_kind === "json" || field.type.toLowerCase().includes("text") || value.includes("\n") || value.length > 120;
3046
3472
  return /* @__PURE__ */ jsxs14(Stack11, { spacing: 1, children: [
3047
- imageUrl ? /* @__PURE__ */ jsx21(
3473
+ imageUrl ? /* @__PURE__ */ jsx22(
3048
3474
  Box11,
3049
3475
  {
3050
3476
  component: "img",
@@ -3061,7 +3487,7 @@ var ReadonlyObjectField = memo7(function ReadonlyObjectField2({
3061
3487
  }
3062
3488
  }
3063
3489
  ) : null,
3064
- /* @__PURE__ */ jsx21(
3490
+ /* @__PURE__ */ jsx22(
3065
3491
  TextField4,
3066
3492
  {
3067
3493
  label: field.label,
@@ -3077,47 +3503,75 @@ var ReadonlyObjectField = memo7(function ReadonlyObjectField2({
3077
3503
  ] });
3078
3504
  });
3079
3505
 
3080
- // src/components/ObjectPage.tsx
3081
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
3082
- var detailResponseCache = /* @__PURE__ */ new Map();
3083
- var inFlightDetailRequests = /* @__PURE__ */ new Map();
3084
- function ObjectPage({ client, slug, id }) {
3085
- var _a, _b, _c, _d;
3086
- const locale = useAdminLocale();
3087
- const t = useAdminTranslation();
3088
- const router = useRouter();
3089
- const pathname = usePathname2();
3090
- const theme = useTheme3();
3091
- const isPhone = useMediaQuery3(theme.breakpoints.down("sm"));
3092
- const cacheKey = `${slug}:${id}`;
3093
- const [data, setData] = useState9(() => {
3506
+ // src/components/object-page/useObjectPageController.ts
3507
+ import { useCallback as useCallback6, useEffect as useEffect10, useMemo as useMemo9, useState as useState10 } from "react";
3508
+
3509
+ // src/utils/isDeepEqual.ts
3510
+ function isDeepEqual(left, right) {
3511
+ if (Object.is(left, right)) {
3512
+ return true;
3513
+ }
3514
+ if (left instanceof Date && right instanceof Date) {
3515
+ return left.getTime() === right.getTime();
3516
+ }
3517
+ if (Array.isArray(left) && Array.isArray(right)) {
3518
+ if (left.length !== right.length) {
3519
+ return false;
3520
+ }
3521
+ return left.every((item, index) => isDeepEqual(item, right[index]));
3522
+ }
3523
+ if (isPlainObject(left) && isPlainObject(right)) {
3524
+ const leftKeys = Object.keys(left).sort();
3525
+ const rightKeys = Object.keys(right).sort();
3526
+ if (!isDeepEqual(leftKeys, rightKeys)) {
3527
+ return false;
3528
+ }
3529
+ return leftKeys.every((key) => isDeepEqual(left[key], right[key]));
3530
+ }
3531
+ return false;
3532
+ }
3533
+ function isPlainObject(value) {
3534
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3535
+ }
3536
+
3537
+ // src/components/object-page/useObjectPageController.ts
3538
+ function useObjectPageController({
3539
+ client,
3540
+ slug,
3541
+ id,
3542
+ listPath,
3543
+ router,
3544
+ t
3545
+ }) {
3546
+ var _a, _b, _c;
3547
+ const cacheKey = buildDetailCacheKey(slug, id);
3548
+ const [data, setData] = useState10(() => {
3094
3549
  var _a2;
3095
- return (_a2 = detailResponseCache.get(cacheKey)) != null ? _a2 : null;
3550
+ return (_a2 = getClientCacheBucket(client).detailResponseCache.get(cacheKey)) != null ? _a2 : null;
3096
3551
  });
3097
- const [values, setValues] = useState9(() => {
3552
+ const [values, setValues] = useState10(() => {
3098
3553
  var _a2, _b2;
3099
- return (_b2 = (_a2 = detailResponseCache.get(cacheKey)) == null ? void 0 : _a2.item) != null ? _b2 : {};
3554
+ return (_b2 = (_a2 = getClientCacheBucket(client).detailResponseCache.get(cacheKey)) == null ? void 0 : _a2.item) != null ? _b2 : {};
3100
3555
  });
3101
- const [initialValues, setInitialValues] = useState9(() => {
3556
+ const [initialValues, setInitialValues] = useState10(() => {
3102
3557
  var _a2, _b2;
3103
- return (_b2 = (_a2 = detailResponseCache.get(cacheKey)) == null ? void 0 : _a2.item) != null ? _b2 : {};
3558
+ return (_b2 = (_a2 = getClientCacheBucket(client).detailResponseCache.get(cacheKey)) == null ? void 0 : _a2.item) != null ? _b2 : {};
3104
3559
  });
3105
- const [error, setError] = useState9(null);
3106
- const [isLoading, setIsLoading] = useState9(data === null);
3107
- const [isSaving, setIsSaving] = useState9(false);
3108
- const [isDeleting, setIsDeleting] = useState9(false);
3109
- const [activeActionSlug, setActiveActionSlug] = useState9(null);
3110
- const [deleteConfirmOpen, setDeleteConfirmOpen] = useState9(false);
3111
- const [deletePreview, setDeletePreview] = useState9(null);
3112
- const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState9(false);
3113
- const [deletePreviewError, setDeletePreviewError] = useState9(null);
3114
- const [actionsAnchorEl, setActionsAnchorEl] = useState9(null);
3115
- const listPath = useMemo8(() => pathname.split("/").slice(0, -1).join("/"), [pathname]);
3116
- const adminRootPath = useMemo8(() => listPath.split("/").slice(0, -1).join("/"), [listPath]);
3117
- useEffect9(() => {
3560
+ const [error, setError] = useState10(null);
3561
+ const [isLoading, setIsLoading] = useState10(data === null);
3562
+ const [isSaving, setIsSaving] = useState10(false);
3563
+ const [isDeleting, setIsDeleting] = useState10(false);
3564
+ const [activeActionSlug, setActiveActionSlug] = useState10(null);
3565
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useState10(false);
3566
+ const [deletePreview, setDeletePreview] = useState10(null);
3567
+ const [isDeletePreviewLoading, setIsDeletePreviewLoading] = useState10(false);
3568
+ const [deletePreviewError, setDeletePreviewError] = useState10(null);
3569
+ const [actionsAnchorEl, setActionsAnchorEl] = useState10(null);
3570
+ useEffect10(() => {
3118
3571
  var _a2;
3119
3572
  let isMounted = true;
3120
- const cachedResponse = detailResponseCache.get(cacheKey);
3573
+ const bucket = getClientCacheBucket(client);
3574
+ const cachedResponse = bucket.detailResponseCache.get(cacheKey);
3121
3575
  if (cachedResponse) {
3122
3576
  setData(cachedResponse);
3123
3577
  setValues(cachedResponse.item);
@@ -3129,10 +3583,13 @@ function ObjectPage({ client, slug, id }) {
3129
3583
  }
3130
3584
  setIsLoading(true);
3131
3585
  setError(null);
3132
- const request = (_a2 = inFlightDetailRequests.get(cacheKey)) != null ? _a2 : client.getItem(slug, id);
3133
- inFlightDetailRequests.set(cacheKey, request);
3586
+ const request = (_a2 = bucket.inFlightDetailRequests.get(cacheKey)) != null ? _a2 : client.getItem(slug, id);
3587
+ bucket.inFlightDetailRequests.set(cacheKey, request);
3588
+ const cacheVersion = getModelCacheVersion(client, slug);
3134
3589
  request.then((response) => {
3135
- detailResponseCache.set(cacheKey, response);
3590
+ if (getModelCacheVersion(client, slug) === cacheVersion) {
3591
+ setCachedDetailResponse(client, cacheKey, response);
3592
+ }
3136
3593
  if (!isMounted) return;
3137
3594
  setData(response);
3138
3595
  setValues(response.item);
@@ -3141,8 +3598,8 @@ function ObjectPage({ client, slug, id }) {
3141
3598
  if (!isMounted) return;
3142
3599
  setError(reason instanceof Error ? reason.message : t("object_load_error"));
3143
3600
  }).finally(() => {
3144
- if (inFlightDetailRequests.get(cacheKey) === request) {
3145
- inFlightDetailRequests.delete(cacheKey);
3601
+ if (bucket.inFlightDetailRequests.get(cacheKey) === request) {
3602
+ bucket.inFlightDetailRequests.delete(cacheKey);
3146
3603
  }
3147
3604
  if (!isMounted) return;
3148
3605
  setIsLoading(false);
@@ -3154,25 +3611,25 @@ function ObjectPage({ client, slug, id }) {
3154
3611
  const meta = (_a = data == null ? void 0 : data.meta) != null ? _a : null;
3155
3612
  const detailFields = (_b = meta == null ? void 0 : meta.detail_fields) != null ? _b : [];
3156
3613
  const objectActions = (_c = meta == null ? void 0 : meta.object_actions) != null ? _c : [];
3157
- const fieldMap = useMemo8(
3614
+ const fieldMap = useMemo9(
3158
3615
  () => {
3159
3616
  var _a2;
3160
3617
  return new Map(((_a2 = meta == null ? void 0 : meta.fields) != null ? _a2 : []).map((field) => [field.name, field]));
3161
3618
  },
3162
3619
  [meta == null ? void 0 : meta.fields]
3163
3620
  );
3164
- const editableFields = useMemo8(
3621
+ const editableFields = useMemo9(
3165
3622
  () => {
3166
3623
  var _a2;
3167
3624
  return ((_a2 = meta == null ? void 0 : meta.fields) != null ? _a2 : []).filter((field) => meta == null ? void 0 : meta.update_fields.includes(field.name));
3168
3625
  },
3169
3626
  [meta == null ? void 0 : meta.fields, meta == null ? void 0 : meta.update_fields]
3170
3627
  );
3171
- const editableFieldNames = useMemo8(
3628
+ const editableFieldNames = useMemo9(
3172
3629
  () => new Set(editableFields.map((field) => field.name)),
3173
3630
  [editableFields]
3174
3631
  );
3175
- const objectTitle = useMemo8(
3632
+ const objectTitle = useMemo9(
3176
3633
  () => {
3177
3634
  var _a2;
3178
3635
  return String((_a2 = data == null ? void 0 : data.item._display) != null ? _a2 : meta ? `${meta.title} #${id}` : id);
@@ -3180,20 +3637,19 @@ function ObjectPage({ client, slug, id }) {
3180
3637
  [data, id, meta]
3181
3638
  );
3182
3639
  const isActionsMenuOpen = actionsAnchorEl !== null;
3183
- useAdminDocumentTitle(t("admin_title"), (_d = meta == null ? void 0 : meta.title) != null ? _d : slug, objectTitle);
3184
- const currentPayload = useMemo8(
3640
+ const currentPayload = useMemo9(
3185
3641
  () => buildAdminPayload(values, editableFields),
3186
3642
  [editableFields, values]
3187
3643
  );
3188
- const initialPayload = useMemo8(
3644
+ const initialPayload = useMemo9(
3189
3645
  () => buildAdminPayload(initialValues, editableFields),
3190
3646
  [editableFields, initialValues]
3191
3647
  );
3192
- const isDirty = useMemo8(
3193
- () => JSON.stringify(currentPayload) !== JSON.stringify(initialPayload),
3648
+ const isDirty = useMemo9(
3649
+ () => !isDeepEqual(currentPayload, initialPayload),
3194
3650
  [currentPayload, initialPayload]
3195
3651
  );
3196
- const handleFieldChange = useCallback5((fieldName, nextValue) => {
3652
+ const handleFieldChange = useCallback6((fieldName, nextValue) => {
3197
3653
  setValues((current) => {
3198
3654
  if (Object.is(current[fieldName], nextValue)) {
3199
3655
  return current;
@@ -3201,7 +3657,7 @@ function ObjectPage({ client, slug, id }) {
3201
3657
  return { ...current, [fieldName]: nextValue };
3202
3658
  });
3203
3659
  }, []);
3204
- const handleSave = useCallback5(async () => {
3660
+ const handleSave = useCallback6(async () => {
3205
3661
  if (!meta || !isDirty) {
3206
3662
  return;
3207
3663
  }
@@ -3210,7 +3666,8 @@ function ObjectPage({ client, slug, id }) {
3210
3666
  try {
3211
3667
  const response = await client.patchItem(slug, id, currentPayload);
3212
3668
  const nextDetail = { meta, item: response.item };
3213
- detailResponseCache.set(cacheKey, nextDetail);
3669
+ invalidateModelCache(client, slug);
3670
+ setCachedDetailResponse(client, cacheKey, nextDetail);
3214
3671
  setData(nextDetail);
3215
3672
  setValues(response.item);
3216
3673
  setInitialValues(response.item);
@@ -3220,40 +3677,31 @@ function ObjectPage({ client, slug, id }) {
3220
3677
  setIsSaving(false);
3221
3678
  }
3222
3679
  }, [cacheKey, client, currentPayload, id, isDirty, meta, slug, t]);
3223
- const handleDelete = useCallback5(async () => {
3680
+ const handleDelete = useCallback6(async () => {
3224
3681
  setIsDeleting(true);
3225
3682
  setError(null);
3226
3683
  try {
3227
3684
  await client.deleteItem(slug, id);
3228
- detailResponseCache.delete(cacheKey);
3685
+ invalidateModelCache(client, slug);
3229
3686
  router.push(listPath);
3230
3687
  } catch (reason) {
3231
3688
  setError(reason instanceof Error ? reason.message : t("object_delete_error"));
3232
3689
  setIsDeleting(false);
3233
3690
  setDeleteConfirmOpen(false);
3234
3691
  }
3235
- }, [cacheKey, client, id, listPath, router, slug, t]);
3236
- const handleNavigateBack = useCallback5(() => {
3237
- if (typeof window !== "undefined" && document.referrer) {
3238
- try {
3239
- const referrerUrl = new URL(document.referrer);
3240
- if (referrerUrl.origin === window.location.origin && referrerUrl.pathname.startsWith(adminRootPath)) {
3241
- router.back();
3242
- return;
3243
- }
3244
- } catch {
3245
- }
3246
- }
3692
+ }, [client, id, listPath, router, slug, t]);
3693
+ const handleNavigateBack = useCallback6(() => {
3247
3694
  router.push(listPath);
3248
- }, [adminRootPath, listPath, router]);
3249
- const handleRunObjectAction = useCallback5(async (actionSlug) => {
3695
+ }, [listPath, router]);
3696
+ const handleRunObjectAction = useCallback6(async (actionSlug) => {
3250
3697
  setActiveActionSlug(actionSlug);
3251
3698
  setError(null);
3252
3699
  try {
3253
3700
  const response = await client.runObjectAction(slug, id, actionSlug);
3254
3701
  const nextDetail = data ? { ...data, item: response.item } : null;
3255
3702
  if (nextDetail) {
3256
- detailResponseCache.set(cacheKey, nextDetail);
3703
+ invalidateModelCache(client, slug);
3704
+ setCachedDetailResponse(client, cacheKey, nextDetail);
3257
3705
  }
3258
3706
  setData(nextDetail);
3259
3707
  setValues(response.item);
@@ -3264,7 +3712,7 @@ function ObjectPage({ client, slug, id }) {
3264
3712
  setActiveActionSlug(null);
3265
3713
  }
3266
3714
  }, [cacheKey, client, data, id, slug, t]);
3267
- const handleOpenDeletePreview = useCallback5(async () => {
3715
+ const handleOpenDeletePreview = useCallback6(async () => {
3268
3716
  setActionsAnchorEl(null);
3269
3717
  setDeleteConfirmOpen(true);
3270
3718
  setDeletePreview(null);
@@ -3279,133 +3727,209 @@ function ObjectPage({ client, slug, id }) {
3279
3727
  setIsDeletePreviewLoading(false);
3280
3728
  }
3281
3729
  }, [client, id, slug, t]);
3282
- if (isLoading && !data) {
3283
- return /* @__PURE__ */ jsx22(ObjectPageSkeleton, {});
3730
+ return {
3731
+ actionsAnchorEl,
3732
+ activeActionSlug,
3733
+ data,
3734
+ deleteConfirmOpen,
3735
+ deletePreview,
3736
+ deletePreviewError,
3737
+ detailFields,
3738
+ editableFieldNames,
3739
+ error,
3740
+ fieldMap,
3741
+ handleDelete,
3742
+ handleFieldChange,
3743
+ handleNavigateBack,
3744
+ handleOpenDeletePreview,
3745
+ handleRunObjectAction,
3746
+ handleSave,
3747
+ initialValues,
3748
+ isActionsMenuOpen,
3749
+ isDeletePreviewLoading,
3750
+ isDeleting,
3751
+ isDirty,
3752
+ isLoading,
3753
+ isSaving,
3754
+ meta,
3755
+ objectActions,
3756
+ objectTitle,
3757
+ setActionsAnchorEl,
3758
+ setDeleteConfirmOpen,
3759
+ setDeletePreview,
3760
+ setDeletePreviewError,
3761
+ values
3762
+ };
3763
+ }
3764
+
3765
+ // src/components/ObjectPage.tsx
3766
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
3767
+ function ObjectPage({ client, slug, id, router }) {
3768
+ var _a, _b;
3769
+ const locale = useAdminLocale();
3770
+ const t = useAdminTranslation();
3771
+ const resolvedRouter = useXLAdminRouter(router);
3772
+ const location = useXLAdminLocation(resolvedRouter);
3773
+ const pathname = location.pathname;
3774
+ const theme = useTheme3();
3775
+ const isPhone = useMediaQuery3(theme.breakpoints.down("sm"));
3776
+ const listPath = pathname.split("/").slice(0, -1).join("/");
3777
+ const controller = useObjectPageController({
3778
+ client,
3779
+ slug,
3780
+ id,
3781
+ listPath,
3782
+ router: resolvedRouter,
3783
+ t
3784
+ });
3785
+ useAdminDocumentTitle(t("admin_title"), (_b = (_a = controller.meta) == null ? void 0 : _a.title) != null ? _b : slug, controller.objectTitle);
3786
+ if (controller.isLoading && !controller.data) {
3787
+ return /* @__PURE__ */ jsx23(ObjectPageSkeleton, {});
3284
3788
  }
3285
- if (error && !data) {
3286
- return /* @__PURE__ */ jsx22(Alert5, { severity: "error", children: error });
3789
+ if (controller.error && !controller.data) {
3790
+ return /* @__PURE__ */ jsx23(Alert6, { severity: "error", children: controller.error });
3287
3791
  }
3288
- if (!data || !meta) {
3289
- return /* @__PURE__ */ jsx22(ObjectPageSkeleton, {});
3792
+ if (!controller.data || !controller.meta) {
3793
+ return /* @__PURE__ */ jsx23(ObjectPageSkeleton, {});
3290
3794
  }
3291
- return /* @__PURE__ */ jsxs15(LocalizationProvider2, { dateAdapter: AdapterDayjs2, adapterLocale: locale, children: [
3795
+ return /* @__PURE__ */ jsx23(XLAdminRouterProvider, { router: resolvedRouter, children: /* @__PURE__ */ jsxs15(LocalizationProvider2, { dateAdapter: AdapterDayjs2, adapterLocale: locale, children: [
3292
3796
  /* @__PURE__ */ jsxs15(Stack12, { spacing: 1.5, sx: { height: "100%", minHeight: 0 }, children: [
3293
- /* @__PURE__ */ jsx22(
3797
+ /* @__PURE__ */ jsx23(
3294
3798
  MainHeader,
3295
3799
  {
3296
- title: objectTitle,
3297
- subtitle: meta.slug,
3298
- beforeSubtitle: /* @__PURE__ */ jsx22(
3800
+ title: controller.objectTitle,
3801
+ subtitle: controller.meta.slug,
3802
+ beforeSubtitle: /* @__PURE__ */ jsx23(
3299
3803
  IconButton4,
3300
3804
  {
3301
3805
  "aria-label": t("back"),
3302
- onClick: handleNavigateBack,
3806
+ onClick: controller.handleNavigateBack,
3303
3807
  size: "small",
3304
3808
  sx: { ml: -0.5 },
3305
- children: /* @__PURE__ */ jsx22(ArrowBackIcon, { fontSize: "small" })
3809
+ children: /* @__PURE__ */ jsx23(ArrowBackIcon, { fontSize: "small" })
3306
3810
  }
3307
3811
  ),
3308
- actions: isPhone ? /* @__PURE__ */ jsx22(
3812
+ actions: isPhone ? /* @__PURE__ */ jsx23(
3309
3813
  IconButton4,
3310
3814
  {
3311
3815
  "aria-label": t("actions"),
3312
- onClick: (event) => setActionsAnchorEl(event.currentTarget),
3816
+ onClick: (event) => controller.setActionsAnchorEl(event.currentTarget),
3313
3817
  size: "small",
3314
3818
  sx: { mr: -0.5 },
3315
- children: /* @__PURE__ */ jsx22(MoreVertIcon2, { fontSize: "small" })
3819
+ children: /* @__PURE__ */ jsx23(MoreVertIcon2, { fontSize: "small" })
3316
3820
  }
3317
3821
  ) : void 0,
3318
- details: meta.description ? /* @__PURE__ */ jsx22(Typography6, { color: "text.secondary", sx: { fontSize: 14, lineHeight: 1.45 }, children: meta.description }) : void 0,
3319
- error
3822
+ details: controller.meta.description ? /* @__PURE__ */ jsx23(Typography6, { color: "text.secondary", sx: { fontSize: 14, lineHeight: 1.45 }, children: controller.meta.description }) : void 0,
3823
+ error: controller.error
3320
3824
  }
3321
3825
  ),
3322
- /* @__PURE__ */ jsxs15(Stack12, { direction: { xs: "column", lg: "row" }, spacing: 1.5, sx: { flex: 1, minHeight: 0, alignItems: "stretch" }, children: [
3323
- /* @__PURE__ */ jsx22(
3324
- Paper8,
3325
- {
3326
- sx: {
3327
- borderRadius: "10px",
3328
- flex: 1,
3329
- minHeight: 0,
3330
- overflow: "hidden"
3331
- },
3332
- children: /* @__PURE__ */ jsx22(Box12, { component: "form", autoComplete: "off", sx: { height: "100%", overflow: "auto", p: 2.5 }, children: /* @__PURE__ */ jsx22(Stack12, { spacing: 1.5, children: detailFields.map((fieldName) => {
3333
- const field = fieldMap.get(fieldName);
3334
- if (!field) {
3335
- return null;
3336
- }
3337
- if (editableFieldNames.has(fieldName)) {
3338
- return /* @__PURE__ */ jsx22(
3339
- ObjectField,
3340
- {
3341
- field,
3342
- value: values[field.name],
3343
- slug,
3344
- client,
3345
- onFieldChange: handleFieldChange
3346
- },
3347
- field.name
3348
- );
3349
- }
3350
- return /* @__PURE__ */ jsx22(
3351
- ReadonlyObjectField,
3352
- {
3353
- field,
3354
- value: formatAdminValue(values[field.name], { locale, field, pretty: true }),
3355
- imageUrl: field.display_kind === "image" ? resolveAdminMediaUrl(values[field.name], field) : null
3826
+ /* @__PURE__ */ jsxs15(
3827
+ Stack12,
3828
+ {
3829
+ direction: { xs: "column", lg: "row" },
3830
+ spacing: 1.5,
3831
+ sx: { flex: 1, minHeight: 0, alignItems: "stretch" },
3832
+ children: [
3833
+ /* @__PURE__ */ jsx23(
3834
+ Paper8,
3835
+ {
3836
+ sx: {
3837
+ borderRadius: "10px",
3838
+ flex: 1,
3839
+ minHeight: 0,
3840
+ overflow: "hidden"
3356
3841
  },
3357
- field.name
3358
- );
3359
- }) }) })
3360
- }
3361
- ),
3362
- /* @__PURE__ */ jsx22(
3363
- Paper8,
3364
- {
3365
- sx: {
3366
- width: { xs: "100%", lg: 280 },
3367
- flexShrink: 0,
3368
- borderRadius: "10px",
3369
- p: 1.5,
3370
- alignSelf: "flex-start",
3371
- position: { lg: "sticky" },
3372
- top: { lg: 0 },
3373
- display: isPhone ? "none" : void 0
3374
- },
3375
- children: /* @__PURE__ */ jsxs15(Stack12, { spacing: 1, children: [
3376
- /* @__PURE__ */ jsx22(Typography6, { variant: "subtitle2", color: "text.secondary", children: t("actions") }),
3377
- isDirty ? /* @__PURE__ */ jsx22(Button5, { variant: "contained", onClick: () => void handleSave(), disabled: isSaving, children: isSaving ? t("saving") : t("save") }) : null,
3378
- /* @__PURE__ */ jsx22(
3379
- Button5,
3380
- {
3381
- variant: "outlined",
3382
- color: "error",
3383
- onClick: () => void handleOpenDeletePreview(),
3384
- disabled: isDeleting,
3385
- children: t("delete")
3386
- }
3387
- ),
3388
- objectActions.map((action) => /* @__PURE__ */ jsx22(
3389
- Button5,
3390
- {
3391
- variant: "outlined",
3392
- onClick: () => void handleRunObjectAction(action.slug),
3393
- disabled: activeActionSlug !== null,
3394
- children: activeActionSlug === action.slug ? t("executing") : action.label
3842
+ children: /* @__PURE__ */ jsx23(Box12, { component: "form", autoComplete: "off", sx: { height: "100%", overflow: "auto", p: 2.5 }, children: /* @__PURE__ */ jsx23(Stack12, { spacing: 1.5, children: controller.detailFields.map((fieldName) => {
3843
+ const field = controller.fieldMap.get(fieldName);
3844
+ if (!field) {
3845
+ return null;
3846
+ }
3847
+ if (controller.editableFieldNames.has(fieldName)) {
3848
+ return /* @__PURE__ */ jsx23(
3849
+ ObjectField,
3850
+ {
3851
+ field,
3852
+ value: controller.values[field.name],
3853
+ slug,
3854
+ client,
3855
+ onFieldChange: controller.handleFieldChange
3856
+ },
3857
+ field.name
3858
+ );
3859
+ }
3860
+ return /* @__PURE__ */ jsx23(
3861
+ ReadonlyObjectField,
3862
+ {
3863
+ field,
3864
+ value: formatAdminValue(controller.values[field.name], {
3865
+ locale,
3866
+ field,
3867
+ pretty: true
3868
+ }),
3869
+ imageUrl: field.display_kind === "image" ? resolveAdminMediaUrl(controller.values[field.name], field) : null
3870
+ },
3871
+ field.name
3872
+ );
3873
+ }) }) })
3874
+ }
3875
+ ),
3876
+ /* @__PURE__ */ jsx23(
3877
+ Paper8,
3878
+ {
3879
+ sx: {
3880
+ width: { xs: "100%", lg: 280 },
3881
+ flexShrink: 0,
3882
+ borderRadius: "10px",
3883
+ p: 1.5,
3884
+ alignSelf: "flex-start",
3885
+ position: { lg: "sticky" },
3886
+ top: { lg: 0 },
3887
+ display: isPhone ? "none" : void 0
3395
3888
  },
3396
- action.slug
3397
- ))
3398
- ] })
3399
- }
3400
- )
3401
- ] })
3889
+ children: /* @__PURE__ */ jsxs15(Stack12, { spacing: 1, children: [
3890
+ /* @__PURE__ */ jsx23(Typography6, { variant: "subtitle2", color: "text.secondary", children: t("actions") }),
3891
+ controller.isDirty ? /* @__PURE__ */ jsx23(
3892
+ Button5,
3893
+ {
3894
+ variant: "contained",
3895
+ onClick: () => void controller.handleSave(),
3896
+ disabled: controller.isSaving,
3897
+ children: controller.isSaving ? t("saving") : t("save")
3898
+ }
3899
+ ) : null,
3900
+ /* @__PURE__ */ jsx23(
3901
+ Button5,
3902
+ {
3903
+ variant: "outlined",
3904
+ color: "error",
3905
+ onClick: () => void controller.handleOpenDeletePreview(),
3906
+ disabled: controller.isDeleting,
3907
+ children: t("delete")
3908
+ }
3909
+ ),
3910
+ controller.objectActions.map((action) => /* @__PURE__ */ jsx23(
3911
+ Button5,
3912
+ {
3913
+ variant: "outlined",
3914
+ onClick: () => void controller.handleRunObjectAction(action.slug),
3915
+ disabled: controller.activeActionSlug !== null,
3916
+ children: controller.activeActionSlug === action.slug ? t("executing") : action.label
3917
+ },
3918
+ action.slug
3919
+ ))
3920
+ ] })
3921
+ }
3922
+ )
3923
+ ]
3924
+ }
3925
+ )
3402
3926
  ] }),
3403
3927
  /* @__PURE__ */ jsxs15(
3404
3928
  Menu2,
3405
3929
  {
3406
- anchorEl: actionsAnchorEl,
3407
- open: isActionsMenuOpen,
3408
- onClose: () => setActionsAnchorEl(null),
3930
+ anchorEl: controller.actionsAnchorEl,
3931
+ open: controller.isActionsMenuOpen,
3932
+ onClose: () => controller.setActionsAnchorEl(null),
3409
3933
  slotProps: {
3410
3934
  paper: {
3411
3935
  sx: {
@@ -3416,67 +3940,66 @@ function ObjectPage({ client, slug, id }) {
3416
3940
  anchorOrigin: { vertical: "bottom", horizontal: "right" },
3417
3941
  transformOrigin: { vertical: "top", horizontal: "right" },
3418
3942
  children: [
3419
- isDirty ? /* @__PURE__ */ jsx22(
3943
+ controller.isDirty ? /* @__PURE__ */ jsx23(
3420
3944
  MenuItem3,
3421
3945
  {
3422
3946
  onClick: () => {
3423
- setActionsAnchorEl(null);
3424
- void handleSave();
3947
+ controller.setActionsAnchorEl(null);
3948
+ void controller.handleSave();
3425
3949
  },
3426
- disabled: isSaving,
3427
- children: isSaving ? t("saving") : t("save")
3950
+ disabled: controller.isSaving,
3951
+ children: controller.isSaving ? t("saving") : t("save")
3428
3952
  }
3429
3953
  ) : null,
3430
- /* @__PURE__ */ jsx22(
3954
+ /* @__PURE__ */ jsx23(
3431
3955
  MenuItem3,
3432
3956
  {
3433
- onClick: () => void handleOpenDeletePreview(),
3434
- disabled: isDeleting,
3957
+ onClick: () => void controller.handleOpenDeletePreview(),
3958
+ disabled: controller.isDeleting,
3435
3959
  sx: { color: "error.main" },
3436
3960
  children: t("delete")
3437
3961
  }
3438
3962
  ),
3439
- objectActions.map((action) => /* @__PURE__ */ jsx22(
3963
+ controller.objectActions.map((action) => /* @__PURE__ */ jsx23(
3440
3964
  MenuItem3,
3441
3965
  {
3442
3966
  onClick: () => {
3443
- setActionsAnchorEl(null);
3444
- void handleRunObjectAction(action.slug);
3967
+ controller.setActionsAnchorEl(null);
3968
+ void controller.handleRunObjectAction(action.slug);
3445
3969
  },
3446
- disabled: activeActionSlug !== null,
3447
- children: activeActionSlug === action.slug ? t("executing") : action.label
3970
+ disabled: controller.activeActionSlug !== null,
3971
+ children: controller.activeActionSlug === action.slug ? t("executing") : action.label
3448
3972
  },
3449
3973
  action.slug
3450
3974
  ))
3451
3975
  ]
3452
3976
  }
3453
3977
  ),
3454
- /* @__PURE__ */ jsx22(
3978
+ /* @__PURE__ */ jsx23(
3455
3979
  DeletePreviewDialog,
3456
3980
  {
3457
- open: deleteConfirmOpen,
3981
+ open: controller.deleteConfirmOpen,
3458
3982
  title: t("delete_object_title"),
3459
- preview: deletePreview,
3460
- error: deletePreviewError,
3461
- isLoading: isDeletePreviewLoading,
3462
- isSubmitting: isDeleting,
3983
+ preview: controller.deletePreview,
3984
+ error: controller.deletePreviewError,
3985
+ isLoading: controller.isDeletePreviewLoading,
3986
+ isSubmitting: controller.isDeleting,
3463
3987
  onClose: () => {
3464
- if (isDeleting) {
3988
+ if (controller.isDeleting) {
3465
3989
  return;
3466
3990
  }
3467
- setDeleteConfirmOpen(false);
3468
- setDeletePreview(null);
3469
- setDeletePreviewError(null);
3991
+ controller.setDeleteConfirmOpen(false);
3992
+ controller.setDeletePreview(null);
3993
+ controller.setDeletePreviewError(null);
3470
3994
  },
3471
- onConfirm: () => void handleDelete()
3995
+ onConfirm: () => void controller.handleDelete()
3472
3996
  }
3473
3997
  )
3474
- ] });
3998
+ ] }) });
3475
3999
  }
3476
4000
 
3477
4001
  // src/components/Shell.tsx
3478
- import { useEffect as useEffect10, useMemo as useMemo9, useState as useState10 } from "react";
3479
- import { usePathname as usePathname5 } from "next/navigation.js";
4002
+ import { useEffect as useEffect11, useMemo as useMemo10, useState as useState11 } from "react";
3480
4003
  import { Box as Box15, CssBaseline, Drawer, GlobalStyles, Stack as Stack13, useMediaQuery as useMediaQuery4 } from "@mui/material";
3481
4004
  import { ThemeProvider } from "@mui/material/styles";
3482
4005
 
@@ -3608,11 +4131,10 @@ var defaultAdminTheme = createTheme({
3608
4131
  });
3609
4132
 
3610
4133
  // src/components/layout/Main.tsx
3611
- import { usePathname as usePathname3 } from "next/navigation.js";
3612
4134
  import { Box as Box13 } from "@mui/material";
3613
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
4135
+ import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
3614
4136
  function Main({ children }) {
3615
- const pathname = usePathname3();
4137
+ const { pathname } = useXLAdminLocation();
3616
4138
  const { pendingPath, pendingView } = useShellContext();
3617
4139
  const normalizeAdminPath2 = (path) => {
3618
4140
  const normalizedPath = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
@@ -3633,7 +4155,7 @@ function Main({ children }) {
3633
4155
  },
3634
4156
  children: [
3635
4157
  children,
3636
- isPendingNavigation ? /* @__PURE__ */ jsx23(
4158
+ isPendingNavigation ? /* @__PURE__ */ jsx24(
3637
4159
  Box13,
3638
4160
  {
3639
4161
  sx: {
@@ -3642,7 +4164,7 @@ function Main({ children }) {
3642
4164
  zIndex: 5,
3643
4165
  backgroundColor: "background.default"
3644
4166
  },
3645
- children: pendingView === "model" || pendingView === "overview" ? /* @__PURE__ */ jsx23(ModelPageSkeleton, {}) : /* @__PURE__ */ jsx23(ModelPageSkeleton, {})
4167
+ children: pendingView === "model" || pendingView === "overview" ? /* @__PURE__ */ jsx24(ModelPageSkeleton, {}) : /* @__PURE__ */ jsx24(ModelPageSkeleton, {})
3646
4168
  }
3647
4169
  ) : null
3648
4170
  ]
@@ -3652,13 +4174,12 @@ function Main({ children }) {
3652
4174
 
3653
4175
  // src/components/layout/Sidebar.tsx
3654
4176
  import { memo as memo8 } from "react";
3655
- import { usePathname as usePathname4 } from "next/navigation.js";
3656
4177
  import { Box as Box14, ListItemButton as ListItemButton3, Typography as Typography7 } from "@mui/material";
3657
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
4178
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
3658
4179
  var Sidebar = memo8(function Sidebar2({ models, blocks, basePath }) {
3659
4180
  var _a;
3660
4181
  const t = useAdminTranslation();
3661
- const pathname = usePathname4();
4182
+ const { pathname } = useXLAdminLocation();
3662
4183
  const { pendingPath, startPendingNavigation } = useShellContext();
3663
4184
  const effectivePathname = pendingPath != null ? pendingPath : pathname;
3664
4185
  const normalizedBasePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
@@ -3667,7 +4188,7 @@ var Sidebar = memo8(function Sidebar2({ models, blocks, basePath }) {
3667
4188
  const relativePath = matchedBasePath ? effectivePathname.slice(matchedBasePath.length) : "";
3668
4189
  const activeModelSlug = (_a = relativePath.split("/").filter(Boolean)[0]) != null ? _a : null;
3669
4190
  const isOverviewActive = matchedBasePath !== null && (effectivePathname === matchedBasePath || effectivePathname === `${matchedBasePath}/`);
3670
- return /* @__PURE__ */ jsx24(Box14, { sx: { height: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsx24(
4191
+ return /* @__PURE__ */ jsx25(Box14, { sx: { height: "100%", overflow: "hidden" }, children: /* @__PURE__ */ jsx25(
3671
4192
  Box14,
3672
4193
  {
3673
4194
  sx: {
@@ -3680,13 +4201,13 @@ var Sidebar = memo8(function Sidebar2({ models, blocks, basePath }) {
3680
4201
  pl: 0
3681
4202
  },
3682
4203
  children: /* @__PURE__ */ jsxs17(Box14, { sx: { direction: "ltr", pl: 1 }, children: [
3683
- /* @__PURE__ */ jsx24(
4204
+ /* @__PURE__ */ jsx25(
3684
4205
  NavLink,
3685
4206
  {
3686
4207
  href: basePath,
3687
4208
  style: { textDecoration: "none", display: "block" },
3688
4209
  onClick: () => startPendingNavigation(basePath, "overview"),
3689
- children: /* @__PURE__ */ jsx24(
4210
+ children: /* @__PURE__ */ jsx25(
3690
4211
  ListItemButton3,
3691
4212
  {
3692
4213
  selected: isOverviewActive,
@@ -3699,12 +4220,12 @@ var Sidebar = memo8(function Sidebar2({ models, blocks, basePath }) {
3699
4220
  backgroundColor: isOverviewActive ? "rgba(255, 255, 255, 0.2)" : "rgba(255, 255, 255, 0.055)"
3700
4221
  }
3701
4222
  },
3702
- children: /* @__PURE__ */ jsx24(Typography7, { variant: "subtitle1", sx: { fontWeight: 700 }, children: t("overview") })
4223
+ children: /* @__PURE__ */ jsx25(Typography7, { variant: "subtitle1", sx: { fontWeight: 700 }, children: t("overview") })
3703
4224
  }
3704
4225
  )
3705
4226
  }
3706
4227
  ),
3707
- /* @__PURE__ */ jsx24(
4228
+ /* @__PURE__ */ jsx25(
3708
4229
  ModelsBlocks,
3709
4230
  {
3710
4231
  models,
@@ -3721,28 +4242,30 @@ var Sidebar = memo8(function Sidebar2({ models, blocks, basePath }) {
3721
4242
  });
3722
4243
 
3723
4244
  // src/components/Shell.tsx
3724
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
4245
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
3725
4246
  function normalizeAdminPath(path) {
3726
4247
  const normalizedPath = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
3727
4248
  return normalizedPath.replace(/^\/(ru|en)(?=\/|$)/, "") || "/";
3728
4249
  }
3729
- function Shell({ client, models, blocks, basePath, locale, children, theme }) {
4250
+ function Shell({ client, models, blocks, basePath, locale, children, theme, router }) {
3730
4251
  void client;
3731
4252
  const activeTheme = theme != null ? theme : defaultAdminTheme;
3732
- const pathname = usePathname5();
4253
+ const resolvedRouter = useXLAdminRouter(router);
4254
+ const location = useXLAdminLocation(resolvedRouter);
4255
+ const pathname = location.pathname;
3733
4256
  const isDesktopSidebar = useMediaQuery4(activeTheme.breakpoints.up("lg"));
3734
- const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState10(false);
3735
- const [pendingPath, setPendingPath] = useState10(null);
3736
- const [pendingView, setPendingView] = useState10(null);
3737
- useEffect10(() => {
4257
+ const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState11(false);
4258
+ const [pendingPath, setPendingPath] = useState11(null);
4259
+ const [pendingView, setPendingView] = useState11(null);
4260
+ useEffect11(() => {
3738
4261
  if (isDesktopSidebar) {
3739
4262
  setIsMobileSidebarOpen(false);
3740
4263
  }
3741
4264
  }, [isDesktopSidebar]);
3742
- useEffect10(() => {
4265
+ useEffect11(() => {
3743
4266
  setIsMobileSidebarOpen(false);
3744
4267
  }, [pathname]);
3745
- useEffect10(() => {
4268
+ useEffect11(() => {
3746
4269
  if (pendingPath === null) {
3747
4270
  return;
3748
4271
  }
@@ -3751,7 +4274,7 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3751
4274
  setPendingView(null);
3752
4275
  }
3753
4276
  }, [pathname, pendingPath]);
3754
- const shellContextValue = useMemo9(() => ({
4277
+ const shellContextValue = useMemo10(() => ({
3755
4278
  isMobile: !isDesktopSidebar,
3756
4279
  openMobileSidebar: () => setIsMobileSidebarOpen(true),
3757
4280
  pendingPath,
@@ -3764,9 +4287,9 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3764
4287
  setPendingView(view);
3765
4288
  }
3766
4289
  }), [isDesktopSidebar, pathname, pendingPath, pendingView]);
3767
- return /* @__PURE__ */ jsx25(ThemeProvider, { theme: activeTheme, children: /* @__PURE__ */ jsx25(AdminLocaleProvider, { locale, children: /* @__PURE__ */ jsx25(AdminDataProvider, { value: { locale: locale === "en" ? "en" : "ru", models, blocks }, children: /* @__PURE__ */ jsxs18(ShellContextProvider, { value: shellContextValue, children: [
3768
- /* @__PURE__ */ jsx25(CssBaseline, {}),
3769
- /* @__PURE__ */ jsx25(
4290
+ return /* @__PURE__ */ jsx26(XLAdminRouterProvider, { router: resolvedRouter, children: /* @__PURE__ */ jsx26(ThemeProvider, { theme: activeTheme, children: /* @__PURE__ */ jsx26(AdminLocaleProvider, { locale, children: /* @__PURE__ */ jsx26(AdminDataProvider, { value: { locale: locale === "en" ? "en" : "ru", models, blocks }, children: /* @__PURE__ */ jsxs18(ShellContextProvider, { value: shellContextValue, children: [
4291
+ /* @__PURE__ */ jsx26(CssBaseline, {}),
4292
+ /* @__PURE__ */ jsx26(
3770
4293
  GlobalStyles,
3771
4294
  {
3772
4295
  styles: (muiTheme) => ({
@@ -3805,7 +4328,7 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3805
4328
  })
3806
4329
  }
3807
4330
  ),
3808
- /* @__PURE__ */ jsx25(
4331
+ /* @__PURE__ */ jsx26(
3809
4332
  Box15,
3810
4333
  {
3811
4334
  "data-xladmin-root": "true",
@@ -3823,7 +4346,7 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3823
4346
  spacing: 0,
3824
4347
  sx: { height: "100%", minHeight: 0, alignItems: "stretch" },
3825
4348
  children: [
3826
- /* @__PURE__ */ jsx25(
4349
+ /* @__PURE__ */ jsx26(
3827
4350
  Box15,
3828
4351
  {
3829
4352
  sx: {
@@ -3835,10 +4358,10 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3835
4358
  pl: 0,
3836
4359
  pr: 1
3837
4360
  },
3838
- children: /* @__PURE__ */ jsx25(Sidebar, { models, blocks, basePath })
4361
+ children: /* @__PURE__ */ jsx26(Sidebar, { models, blocks, basePath })
3839
4362
  }
3840
4363
  ),
3841
- /* @__PURE__ */ jsx25(
4364
+ /* @__PURE__ */ jsx26(
3842
4365
  Box15,
3843
4366
  {
3844
4367
  sx: {
@@ -3852,7 +4375,7 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3852
4375
  py: { xs: 1, sm: 1.5, lg: 2 },
3853
4376
  pl: { xs: 1, sm: 1.5, lg: 1 }
3854
4377
  },
3855
- children: /* @__PURE__ */ jsx25(Main, { children })
4378
+ children: /* @__PURE__ */ jsx26(Main, { children })
3856
4379
  }
3857
4380
  )
3858
4381
  ]
@@ -3860,7 +4383,7 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3860
4383
  )
3861
4384
  }
3862
4385
  ),
3863
- /* @__PURE__ */ jsx25(
4386
+ /* @__PURE__ */ jsx26(
3864
4387
  Drawer,
3865
4388
  {
3866
4389
  anchor: "left",
@@ -3879,10 +4402,10 @@ function Shell({ client, models, blocks, basePath, locale, children, theme }) {
3879
4402
  }
3880
4403
  }
3881
4404
  },
3882
- children: /* @__PURE__ */ jsx25(Box15, { sx: { height: "100%", minHeight: 0, pr: 1.5 }, children: /* @__PURE__ */ jsx25(Sidebar, { models, blocks, basePath }) })
4405
+ children: /* @__PURE__ */ jsx26(Box15, { sx: { height: "100%", minHeight: 0, pr: 1.5 }, children: /* @__PURE__ */ jsx26(Sidebar, { models, blocks, basePath }) })
3883
4406
  }
3884
4407
  )
3885
- ] }) }) }) });
4408
+ ] }) }) }) }) });
3886
4409
  }
3887
4410
  export {
3888
4411
  AdminLocaleProvider,
@@ -3898,13 +4421,19 @@ export {
3898
4421
  OverviewPage,
3899
4422
  Shell,
3900
4423
  Sidebar,
4424
+ XLAdminRouterProvider,
4425
+ buildUrlWithParams,
3901
4426
  createAxiosXLAdminClient,
4427
+ createBrowserXLAdminRouter,
3902
4428
  createFetchXLAdminClient,
3903
4429
  createFetchXLAdminTransport,
3904
4430
  createXLAdminClient,
3905
4431
  defaultAdminTheme,
4432
+ handleNavLinkClick,
3906
4433
  normalizeAdminLocale,
3907
4434
  translateAdmin,
3908
4435
  useAdminLocale,
3909
- useAdminTranslation
4436
+ useAdminTranslation,
4437
+ useXLAdminLocation,
4438
+ useXLAdminRouter
3910
4439
  };