@plumile/backoffice-react 0.1.205 → 0.1.206

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.
Files changed (30) hide show
  1. package/README.md +7 -0
  2. package/lib/esm/components/backoffice/pickers/EntityIdPickerDialog.js +123 -70
  3. package/lib/esm/components/backoffice/pickers/EntityIdPickerDialog.js.map +1 -1
  4. package/lib/esm/components/backoffice/scaffolds/BackofficeEntityListScaffold.js +151 -149
  5. package/lib/esm/components/backoffice/scaffolds/BackofficeEntityListScaffold.js.map +1 -1
  6. package/lib/esm/hooks/useInfiniteConnection.js +40 -38
  7. package/lib/esm/hooks/useInfiniteConnection.js.map +1 -1
  8. package/lib/esm/i18n/locales/en/backofficeReact.js +1 -0
  9. package/lib/esm/i18n/locales/en/backofficeReact.js.map +1 -1
  10. package/lib/esm/i18n/locales/fr/backofficeReact.js +1 -0
  11. package/lib/esm/i18n/locales/fr/backofficeReact.js.map +1 -1
  12. package/lib/esm/index.js +42 -42
  13. package/lib/esm/pages/BackofficeEntityListDataPage.js +39 -31
  14. package/lib/esm/pages/BackofficeEntityListDataPage.js.map +1 -1
  15. package/lib/esm/pages/BackofficeEntityListPage.js +2 -0
  16. package/lib/esm/pages/BackofficeEntityListPage.js.map +1 -1
  17. package/lib/types/components/backoffice/pickers/EntityIdPickerDialog.d.ts.map +1 -1
  18. package/lib/types/components/backoffice/scaffolds/BackofficeEntityListScaffold.d.ts +2 -0
  19. package/lib/types/components/backoffice/scaffolds/BackofficeEntityListScaffold.d.ts.map +1 -1
  20. package/lib/types/hooks/useInfiniteConnection.d.ts +3 -2
  21. package/lib/types/hooks/useInfiniteConnection.d.ts.map +1 -1
  22. package/lib/types/i18n/resources.d.ts +2 -0
  23. package/lib/types/i18n/resources.d.ts.map +1 -1
  24. package/lib/types/pages/BackofficeEntityListDataPage.d.ts.map +1 -1
  25. package/lib/types/pages/BackofficeEntityListPage.d.ts.map +1 -1
  26. package/package.json +6 -6
  27. package/lib/esm/hooks/useBackofficeLoadMore.js +0 -18
  28. package/lib/esm/hooks/useBackofficeLoadMore.js.map +0 -1
  29. package/lib/types/hooks/useBackofficeLoadMore.d.ts +0 -9
  30. package/lib/types/hooks/useBackofficeLoadMore.d.ts.map +0 -1
package/README.md CHANGED
@@ -91,6 +91,11 @@ without loading the list facet.
91
91
 
92
92
  ### Relay and mutation helpers
93
93
 
94
+ - `useInfiniteConnection` is the required connection lifecycle for generic and
95
+ application lists. Callers provide a telemetry `surface`, a variable-derived
96
+ `resetKey`, and adapt Relay `loadNext(count, { onComplete })` through
97
+ `runLoadMore`; the hook owns deduplication, timeout, incremental error, and
98
+ retry behavior.
94
99
  - `resolveMutationOutcome`
95
100
  - `resolveAgentStartOutcome`
96
101
  - `requireField`
@@ -112,6 +117,8 @@ only helpers tied to backoffice mutations, connections, and typed data.
112
117
  `BackofficeLifecycleTimelineSection` and `BackofficeEntityLink`
113
118
  - `EntityIdPickerDialog`, which remains the backoffice picker integration for
114
119
  manifests, picker facets, Relay loading, scope, search, and selected IDs.
120
+ It debounces search, paginates the manifest connection, and observes the
121
+ modal content scroll root while preserving local incremental retry.
115
122
  The reusable picker visuals live in `@plumile/ui` as
116
123
  `BackofficePickerShell`, `BackofficePickerList`, and `BackofficePickerRow`.
117
124
 
@@ -1,126 +1,179 @@
1
1
  import { useBackofficeReactTranslation as e } from "../../../i18n/useBackofficeReactTranslation.js";
2
2
  import { BackofficeErrorBoundary as t } from "../errors/BackofficeErrorBoundary.js";
3
3
  import { useBackofficePickerEntityLoader as n } from "../../../provider/useBackofficeEntityLoader.js";
4
- import { useBackofficeRuntimeFragment as r } from "../../../relay/typedRelayHooks.js";
5
- import { loadingLabel as i, loadingState as a } from "./entityIdPickerDialog.css.js";
6
- import { Suspense as o, useCallback as s, useEffect as c, useMemo as l, useState as u } from "react";
7
- import { InlineBanner as d } from "@plumile/ui/components/feedback/InlineBanner.js";
8
- import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
9
- import { useTranslation as h } from "react-i18next";
10
- import { Button as g } from "@plumile/ui/atomic/atoms/button/Button.js";
11
- import * as _ from "react-relay";
12
- import { Modal as v } from "@plumile/ui/atomic/atoms/modal/Modal.js";
13
- import { Spinner as y } from "@plumile/ui/backoffice/atoms/spinner/Spinner.js";
14
- import { BackofficeEmptyState as b } from "@plumile/ui/backoffice/molecules/backoffice_empty_state/BackofficeEmptyState.js";
15
- import { BackofficePickerList as x, BackofficePickerShell as S } from "@plumile/ui/backoffice/molecules/backoffice_picker/BackofficePicker.js";
4
+ import r from "../../../hooks/useInfiniteConnection.js";
5
+ import { useBackofficeRuntimePaginationFragment as i } from "../../../relay/typedRelayHooks.js";
6
+ import { loadingLabel as a, loadingState as o } from "./entityIdPickerDialog.css.js";
7
+ import { Suspense as s, useCallback as c, useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
8
+ import { InlineBanner as p } from "@plumile/ui/components/feedback/InlineBanner.js";
9
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
10
+ import { useTranslation as _ } from "react-i18next";
11
+ import { Button as v } from "@plumile/ui/atomic/atoms/button/Button.js";
12
+ import * as y from "react-relay";
13
+ import { Modal as b } from "@plumile/ui/atomic/atoms/modal/Modal.js";
14
+ import { Spinner as x } from "@plumile/ui/backoffice/atoms/spinner/Spinner.js";
15
+ import { BackofficeEmptyState as S } from "@plumile/ui/backoffice/molecules/backoffice_empty_state/BackofficeEmptyState.js";
16
+ import { BackofficePickerList as C, BackofficePickerShell as w } from "@plumile/ui/backoffice/molecules/backoffice_picker/BackofficePicker.js";
17
+ import { InfiniteScrollTrigger as T } from "@plumile/ui/components/infinite/InfiniteScrollTrigger.js";
16
18
  //#region src/components/backoffice/pickers/EntityIdPickerDialog.tsx
17
- var { useLazyLoadQuery: C } = _, w = "store-and-network", T = (e, t) => e(t), E = ({ config: e, search: t, scope: n, fetchKey: i, onSelectId: a }) => {
18
- let o = l(() => {
19
- let r = t.trim();
19
+ var { useLazyLoadQuery: E } = y, D = "store-and-network", O = (e, t) => e(t), k = ({ config: e, entity: t, search: n, scope: a, fetchKey: o, resetKey: s, scrollRootRef: l, labels: d, onSelectId: f }) => {
20
+ let _ = u(() => {
21
+ let t = n.trim();
20
22
  return e.buildQueryVariables({
21
- search: r,
22
- scope: n
23
+ search: t,
24
+ scope: a
23
25
  });
24
26
  }, [
25
27
  e,
26
- n,
27
- t
28
- ]), s = C(e.query, o, {
29
- fetchPolicy: w,
30
- fetchKey: i
31
- }), c = r(e.fragment, s), u = e.getConnection(c), d = l(() => u.edges.map((t) => {
28
+ a,
29
+ n
30
+ ]), y = E(e.query, _, {
31
+ fetchPolicy: D,
32
+ fetchKey: o
33
+ }), { data: b, loadNext: x, hasNext: S, isLoadingNext: w } = i(e.fragment, y), O = e.getConnection(b), k = u(() => O.edges.map((t) => {
32
34
  let n = e.toRow(t.node);
33
35
  return {
34
36
  id: e.getRowId(n),
35
37
  title: n.title,
36
38
  subtitle: n.subtitle
37
39
  };
38
- }), [e, u.edges]);
39
- return /* @__PURE__ */ p(x, {
40
- items: d,
41
- onSelectItem: (e) => {
42
- a(e.id);
43
- }
40
+ }), [e, O.edges]), A = c((e) => {
41
+ x(20, { onComplete: e });
42
+ }, [x]), j = r({
43
+ nodes: k,
44
+ hasNext: S,
45
+ isLoadingNext: w,
46
+ runLoadMore: A,
47
+ surface: `backoffice.entity-picker.${t}`,
48
+ resetKey: s
44
49
  });
45
- }, D = (e, t) => /* @__PURE__ */ p(f, { children: /* @__PURE__ */ p(g, {
50
+ return /* @__PURE__ */ g(m, { children: [
51
+ /* @__PURE__ */ h(C, {
52
+ items: j.nodes,
53
+ onSelectItem: (e) => {
54
+ f(e.id);
55
+ }
56
+ }),
57
+ /* @__PURE__ */ h(T, {
58
+ disabled: j.incrementalError != null,
59
+ hasNext: j.hasNext,
60
+ isLoading: j.isLoadingNext,
61
+ onLoadMore: j.loadMoreSafe,
62
+ scrollRootRef: l,
63
+ labels: {
64
+ loadMore: d.loadMore,
65
+ loading: d.loading,
66
+ end: d.end
67
+ }
68
+ }),
69
+ j.incrementalError != null && /* @__PURE__ */ h(p, {
70
+ tone: "danger",
71
+ title: j.incrementalError.message,
72
+ actions: /* @__PURE__ */ h(v, {
73
+ type: "button",
74
+ variant: "secondary",
75
+ onClick: j.retryLoadMore,
76
+ children: d.retry
77
+ }),
78
+ children: null
79
+ })
80
+ ] });
81
+ }, A = (e, t) => /* @__PURE__ */ h(m, { children: /* @__PURE__ */ h(v, {
46
82
  type: "button",
47
83
  variant: "secondary",
48
84
  onClick: e,
49
85
  children: t
50
- }) }), O = ({ message: e }) => /* @__PURE__ */ p(d, {
86
+ }) }), j = ({ message: e }) => /* @__PURE__ */ h(p, {
51
87
  tone: "warning",
52
88
  title: e,
53
89
  children: null
54
- }), k = ({ label: e }) => /* @__PURE__ */ m("div", {
55
- className: a,
90
+ }), M = ({ label: e }) => /* @__PURE__ */ g("div", {
91
+ className: o,
56
92
  role: "status",
57
93
  "aria-live": "polite",
58
- children: [/* @__PURE__ */ p(y, {
94
+ children: [/* @__PURE__ */ h(x, {
59
95
  size: 18,
60
96
  ariaLabel: e
61
- }), /* @__PURE__ */ p("span", {
62
- className: i,
97
+ }), /* @__PURE__ */ h("span", {
98
+ className: a,
63
99
  children: e
64
100
  })]
65
- }), A = ({ isOpen: r, entity: i, title: a, scope: f, onClose: m, onSelectId: _ }) => {
66
- let { t: y } = h(), { t: x } = e(), [C, w] = u(""), [A, j] = u(0), M = n(i, { enabled: r }), N;
67
- M.status === "loaded" && (N = M.module.config.picker), c(() => {
68
- r && (w(""), j((e) => e + 1));
69
- }, [r]);
70
- let P = l(() => D(m, x("common.actions.close")), [m, x]), F = s((e) => {
71
- w(e), j((e) => e + 1);
72
- }, []), I = s((e) => {
73
- _(e), m();
74
- }, [m, _]);
101
+ }), N = ({ isOpen: r, entity: i, title: a, scope: o, onClose: m, onSelectId: g }) => {
102
+ let { t: y } = _(), { t: x } = e(), [C, T] = f(""), [E, D] = f(""), [N, P] = f(0), F = d(null), I = n(i, { enabled: r }), L;
103
+ I.status === "loaded" && (L = I.module.config.picker), l(() => {
104
+ r && (T(""), D(""), P((e) => e + 1));
105
+ }, [r]), l(() => {
106
+ let e = setTimeout(() => {
107
+ D(C);
108
+ }, 250);
109
+ return () => {
110
+ clearTimeout(e);
111
+ };
112
+ }, [C]);
113
+ let R = u(() => A(m, x("common.actions.close")), [m, x]), z = c((e) => {
114
+ T(e);
115
+ }, []), B = c((e) => {
116
+ g(e), m();
117
+ }, [m, g]);
75
118
  if (!r) return null;
76
- let L;
77
- N?.searchPlaceholder != null && (L = T(N.searchPlaceholder, y));
78
- let R = C.trim(), z = N?.searchRequired === !0, B;
79
- return B = M.status === "loading" ? /* @__PURE__ */ p(k, { label: x("common.loading") }) : N == null ? /* @__PURE__ */ p(O, { message: x("picker.unavailable", { entity: i }) }) : z && R === "" ? /* @__PURE__ */ p(b, { title: x("picker.searchRequired") }) : /* @__PURE__ */ p(E, {
80
- config: N,
81
- search: C,
82
- scope: f,
83
- fetchKey: A,
84
- onSelectId: I
85
- }), /* @__PURE__ */ p(v, {
119
+ let V;
120
+ L?.searchPlaceholder != null && (V = O(L.searchPlaceholder, y));
121
+ let H = E.trim(), U = L?.searchRequired === !0, W;
122
+ return W = I.status === "loading" ? /* @__PURE__ */ h(M, { label: x("common.loading") }) : L == null ? /* @__PURE__ */ h(j, { message: x("picker.unavailable", { entity: i }) }) : U && H === "" ? /* @__PURE__ */ h(S, { title: x("picker.searchRequired") }) : /* @__PURE__ */ h(k, {
123
+ config: L,
124
+ entity: i,
125
+ search: E,
126
+ scope: o,
127
+ fetchKey: N,
128
+ resetKey: `${i}:${H}:${JSON.stringify(o ?? {})}:${N}`,
129
+ scrollRootRef: F,
130
+ labels: {
131
+ loadMore: x("list.loadMore.action"),
132
+ loading: x("list.loadMore.loading"),
133
+ end: x("list.loadMore.end"),
134
+ retry: x("common.actions.retry")
135
+ },
136
+ onSelectId: B
137
+ }), /* @__PURE__ */ h(b, {
86
138
  isOpen: r,
87
139
  onClose: m,
88
140
  title: a,
89
- footer: P,
141
+ footer: R,
90
142
  initialFocus: "first-form-control",
91
- children: /* @__PURE__ */ p(S, {
143
+ scrollRootRef: F,
144
+ children: /* @__PURE__ */ h(w, {
92
145
  searchValue: C,
93
- onSearchChange: F,
94
- searchPlaceholder: L,
95
- searchAriaLabel: L,
146
+ onSearchChange: z,
147
+ searchPlaceholder: V,
148
+ searchAriaLabel: V,
96
149
  searchName: "backoffice-entity-picker-search",
97
- searchEnabled: N?.searchEnabled ?? !0,
98
- children: /* @__PURE__ */ p(t, {
150
+ searchEnabled: L?.searchEnabled ?? !0,
151
+ children: /* @__PURE__ */ h(t, {
99
152
  fallback: (e) => {
100
153
  let { reset: t } = e;
101
- return /* @__PURE__ */ p(d, {
154
+ return /* @__PURE__ */ h(p, {
102
155
  tone: "danger",
103
156
  title: x("picker.errors.loadFailed"),
104
- actions: /* @__PURE__ */ p(g, {
157
+ actions: /* @__PURE__ */ h(v, {
105
158
  type: "button",
106
159
  variant: "secondary",
107
160
  onClick: () => {
108
- t(), j((e) => e + 1);
161
+ t(), P((e) => e + 1);
109
162
  },
110
163
  children: x("common.actions.retry")
111
164
  }),
112
165
  children: null
113
166
  });
114
167
  },
115
- children: /* @__PURE__ */ p(o, {
116
- fallback: /* @__PURE__ */ p(k, { label: x("common.loading") }),
117
- children: B
168
+ children: /* @__PURE__ */ h(s, {
169
+ fallback: /* @__PURE__ */ h(M, { label: x("common.loading") }),
170
+ children: W
118
171
  })
119
172
  })
120
173
  })
121
174
  });
122
175
  };
123
176
  //#endregion
124
- export { A as EntityIdPickerDialog, A as default };
177
+ export { N as EntityIdPickerDialog, N as default };
125
178
 
126
179
  //# sourceMappingURL=EntityIdPickerDialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityIdPickerDialog.js","names":[],"sources":["../../../../../src/components/backoffice/pickers/EntityIdPickerDialog.tsx"],"sourcesContent":["import {\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useState,\n type JSX,\n} from 'react';\nimport type { TFunction } from 'i18next';\nimport { useTranslation } from 'react-i18next';\nimport * as ReactRelay from 'react-relay';\n\nimport { Button } from '@plumile/ui/atomic/atoms/button/Button.js';\nimport { Modal } from '@plumile/ui/atomic/atoms/modal/Modal.js';\nimport { Spinner } from '@plumile/ui/backoffice/atoms/spinner/Spinner.js';\nimport { BackofficeEmptyState } from '@plumile/ui/backoffice/molecules/backoffice_empty_state/BackofficeEmptyState.js';\nimport {\n BackofficePickerList,\n BackofficePickerShell,\n type BackofficePickerItem,\n} from '@plumile/ui/backoffice/molecules/backoffice_picker/BackofficePicker.js';\nimport { InlineBanner } from '@plumile/ui/components/feedback/InlineBanner.js';\nimport type {\n BackofficePickerScope,\n BackofficeRuntimeEntityPickerConfig,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\n\nimport { BackofficeErrorBoundary } from '../errors/BackofficeErrorBoundary.js';\nimport { useBackofficeReactTranslation } from '../../../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficePickerEntityLoader } from '../../../provider/useBackofficeEntityLoader.js';\nimport { useBackofficeRuntimeFragment } from '../../../relay/typedRelayHooks.js';\n\nimport * as styles from './entityIdPickerDialog.css.js';\n\nconst { useLazyLoadQuery } = ReactRelay;\n\nexport type EntityIdPickerDialogProps = {\n isOpen: boolean;\n entity: string;\n title: string;\n scope?: BackofficePickerScope<Record<string, string>>;\n onClose: () => void;\n onSelectId: (id: string) => void;\n};\n\nconst PICKER_FETCH_POLICY = 'store-and-network' as const;\n\nconst resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\ntype PickerBodyProps = {\n config: BackofficeRuntimeEntityPickerConfig;\n search: string;\n scope?: BackofficePickerScope<Record<string, string>>;\n fetchKey: number;\n onSelectId: (id: string) => void;\n};\n\ntype RuntimePickerRow = {\n title: string;\n subtitle?: string | null | undefined;\n};\n\nconst PickerBody = ({\n config,\n search,\n scope,\n fetchKey,\n onSelectId,\n}: PickerBodyProps): JSX.Element => {\n const variables = useMemo(() => {\n const trimmed = search.trim();\n return config.buildQueryVariables({ search: trimmed, scope });\n }, [config, scope, search]);\n\n const queryData = useLazyLoadQuery(config.query, variables, {\n fetchPolicy: PICKER_FETCH_POLICY,\n fetchKey,\n });\n\n const fragmentData = useBackofficeRuntimeFragment(config.fragment, queryData);\n\n const connection = config.getConnection(fragmentData);\n\n const items = useMemo<readonly BackofficePickerItem[]>(() => {\n return connection.edges.map((edge) => {\n const row = config.toRow(edge.node) as RuntimePickerRow;\n const id = config.getRowId(row);\n return {\n id,\n title: row.title,\n subtitle: row.subtitle,\n };\n });\n }, [config, connection.edges]);\n\n return (\n <BackofficePickerList\n items={items}\n onSelectItem={(item) => {\n onSelectId(item.id);\n }}\n />\n );\n};\n\nconst buildFooter = (onClose: () => void, closeLabel: string): JSX.Element => {\n return (\n <>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n {closeLabel}\n </Button>\n </>\n );\n};\n\nconst PickerUnavailable = ({ message }: { message: string }) => {\n return (\n <InlineBanner tone=\"warning\" title={message}>\n {null}\n </InlineBanner>\n );\n};\n\nconst PickerLoading = ({ label }: { label: string }): JSX.Element => {\n return (\n <div className={styles.loadingState} role=\"status\" aria-live=\"polite\">\n <Spinner size={18} ariaLabel={label} />\n <span className={styles.loadingLabel}>{label}</span>\n </div>\n );\n};\n\nexport const EntityIdPickerDialog = ({\n isOpen,\n entity,\n title,\n scope,\n onClose,\n onSelectId,\n}: EntityIdPickerDialogProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const [search, setSearch] = useState('');\n const [fetchKey, setFetchKey] = useState(0);\n const entityState = useBackofficePickerEntityLoader(entity, {\n enabled: isOpen,\n });\n\n let pickerConfig: BackofficeRuntimeEntityPickerConfig | undefined;\n if (entityState.status === 'loaded') {\n pickerConfig = entityState.module.config.picker as\n BackofficeRuntimeEntityPickerConfig | undefined;\n }\n\n useEffect(() => {\n if (isOpen) {\n setSearch('');\n setFetchKey((value) => {\n return value + 1;\n });\n }\n }, [isOpen]);\n\n const footer = useMemo(() => {\n return buildFooter(onClose, t('common.actions.close'));\n }, [onClose, t]);\n\n const handleSearchChange = useCallback((next: string) => {\n setSearch(next);\n setFetchKey((value) => {\n return value + 1;\n });\n }, []);\n\n const handleSelectId = useCallback(\n (id: string) => {\n onSelectId(id);\n onClose();\n },\n [onClose, onSelectId],\n );\n\n if (!isOpen) {\n return null;\n }\n\n let resolvedSearchPlaceholder: string | undefined;\n if (pickerConfig?.searchPlaceholder != null) {\n resolvedSearchPlaceholder = resolveLabel(\n pickerConfig.searchPlaceholder,\n tApp,\n );\n }\n\n const trimmedSearch = search.trim();\n const isSearchRequired = pickerConfig?.searchRequired === true;\n\n let pickerNode: JSX.Element;\n if (entityState.status === 'loading') {\n pickerNode = <PickerLoading label={t('common.loading')} />;\n } else if (pickerConfig != null) {\n if (isSearchRequired && trimmedSearch === '') {\n pickerNode = <BackofficeEmptyState title={t('picker.searchRequired')} />;\n } else {\n pickerNode = (\n <PickerBody\n config={pickerConfig}\n search={search}\n scope={scope}\n fetchKey={fetchKey}\n onSelectId={handleSelectId}\n />\n );\n }\n } else {\n pickerNode = (\n <PickerUnavailable message={t('picker.unavailable', { entity })} />\n );\n }\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n footer={footer}\n initialFocus=\"first-form-control\"\n >\n <BackofficePickerShell\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={resolvedSearchPlaceholder}\n searchAriaLabel={resolvedSearchPlaceholder}\n searchName=\"backoffice-entity-picker-search\"\n searchEnabled={pickerConfig?.searchEnabled ?? true}\n >\n <BackofficeErrorBoundary\n fallback={(args: { error: unknown; reset: () => void }) => {\n const { reset } = args;\n return (\n <InlineBanner\n tone=\"danger\"\n title={t('picker.errors.loadFailed')}\n actions={\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n reset();\n setFetchKey((value) => {\n return value + 1;\n });\n }}\n >\n {t('common.actions.retry')}\n </Button>\n }\n >\n {null}\n </InlineBanner>\n );\n }}\n >\n <Suspense fallback={<PickerLoading label={t('common.loading')} />}>\n {pickerNode}\n </Suspense>\n </BackofficeErrorBoundary>\n </BackofficePickerShell>\n </Modal>\n );\n};\n\nexport type { BackofficePickerScope } from '@plumile/backoffice-core/types.js';\n\nexport default EntityIdPickerDialog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntityIdPickerDialog.js","names":[],"sources":["../../../../../src/components/backoffice/pickers/EntityIdPickerDialog.tsx"],"sourcesContent":["import {\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n type RefObject,\n} from 'react';\nimport type { TFunction } from 'i18next';\nimport { useTranslation } from 'react-i18next';\nimport * as ReactRelay from 'react-relay';\n\nimport { Button } from '@plumile/ui/atomic/atoms/button/Button.js';\nimport { Modal } from '@plumile/ui/atomic/atoms/modal/Modal.js';\nimport { Spinner } from '@plumile/ui/backoffice/atoms/spinner/Spinner.js';\nimport { BackofficeEmptyState } from '@plumile/ui/backoffice/molecules/backoffice_empty_state/BackofficeEmptyState.js';\nimport {\n BackofficePickerList,\n BackofficePickerShell,\n type BackofficePickerItem,\n} from '@plumile/ui/backoffice/molecules/backoffice_picker/BackofficePicker.js';\nimport { InlineBanner } from '@plumile/ui/components/feedback/InlineBanner.js';\nimport { InfiniteScrollTrigger } from '@plumile/ui/components/infinite/InfiniteScrollTrigger.js';\nimport type {\n BackofficePickerScope,\n BackofficeRuntimeEntityPickerConfig,\n I18nLabel,\n} from '@plumile/backoffice-core/types.js';\n\nimport { BackofficeErrorBoundary } from '../errors/BackofficeErrorBoundary.js';\nimport { useBackofficeReactTranslation } from '../../../i18n/useBackofficeReactTranslation.js';\nimport { useBackofficePickerEntityLoader } from '../../../provider/useBackofficeEntityLoader.js';\nimport { useInfiniteConnection } from '../../../hooks/useInfiniteConnection.js';\nimport { useBackofficeRuntimePaginationFragment } from '../../../relay/typedRelayHooks.js';\n\nimport * as styles from './entityIdPickerDialog.css.js';\n\nconst { useLazyLoadQuery } = ReactRelay;\n\nexport type EntityIdPickerDialogProps = {\n isOpen: boolean;\n entity: string;\n title: string;\n scope?: BackofficePickerScope<Record<string, string>>;\n onClose: () => void;\n onSelectId: (id: string) => void;\n};\n\nconst PICKER_FETCH_POLICY = 'store-and-network' as const;\n\nconst resolveLabel = (label: I18nLabel, tApp: TFunction): string => {\n return label(tApp);\n};\n\ntype PickerBodyProps = {\n config: BackofficeRuntimeEntityPickerConfig;\n entity: string;\n search: string;\n scope?: BackofficePickerScope<Record<string, string>>;\n fetchKey: number;\n resetKey: string;\n scrollRootRef: RefObject<Element | null>;\n labels: {\n loadMore: string;\n loading: string;\n end: string;\n retry: string;\n };\n onSelectId: (id: string) => void;\n};\n\ntype RuntimePickerRow = {\n title: string;\n subtitle?: string | null | undefined;\n};\n\nconst PickerBody = ({\n config,\n entity,\n search,\n scope,\n fetchKey,\n resetKey,\n scrollRootRef,\n labels,\n onSelectId,\n}: PickerBodyProps): JSX.Element => {\n const variables = useMemo(() => {\n const trimmed = search.trim();\n return config.buildQueryVariables({ search: trimmed, scope });\n }, [config, scope, search]);\n\n const queryData = useLazyLoadQuery(config.query, variables, {\n fetchPolicy: PICKER_FETCH_POLICY,\n fetchKey,\n });\n\n const {\n data: fragmentData,\n loadNext,\n hasNext,\n isLoadingNext,\n } = useBackofficeRuntimePaginationFragment(config.fragment, queryData);\n\n const connection = config.getConnection(fragmentData);\n\n const items = useMemo<readonly BackofficePickerItem[]>(() => {\n return connection.edges.map((edge) => {\n const row = config.toRow(edge.node) as RuntimePickerRow;\n const id = config.getRowId(row);\n return {\n id,\n title: row.title,\n subtitle: row.subtitle,\n };\n });\n }, [config, connection.edges]);\n\n const runLoadMore = useCallback(\n (onComplete: (error: Error | null) => void) => {\n loadNext(20, { onComplete });\n },\n [loadNext],\n );\n const pagination = useInfiniteConnection({\n nodes: items,\n hasNext,\n isLoadingNext,\n runLoadMore,\n surface: `backoffice.entity-picker.${entity}`,\n resetKey,\n });\n\n return (\n <>\n <BackofficePickerList\n items={pagination.nodes}\n onSelectItem={(item) => {\n onSelectId(item.id);\n }}\n />\n <InfiniteScrollTrigger\n disabled={pagination.incrementalError != null}\n hasNext={pagination.hasNext}\n isLoading={pagination.isLoadingNext}\n onLoadMore={pagination.loadMoreSafe}\n scrollRootRef={scrollRootRef}\n labels={{\n loadMore: labels.loadMore,\n loading: labels.loading,\n end: labels.end,\n }}\n />\n {pagination.incrementalError != null && (\n <InlineBanner\n tone=\"danger\"\n title={pagination.incrementalError.message}\n actions={\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={pagination.retryLoadMore}\n >\n {labels.retry}\n </Button>\n }\n >\n {null}\n </InlineBanner>\n )}\n </>\n );\n};\n\nconst buildFooter = (onClose: () => void, closeLabel: string): JSX.Element => {\n return (\n <>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n {closeLabel}\n </Button>\n </>\n );\n};\n\nconst PickerUnavailable = ({ message }: { message: string }) => {\n return (\n <InlineBanner tone=\"warning\" title={message}>\n {null}\n </InlineBanner>\n );\n};\n\nconst PickerLoading = ({ label }: { label: string }): JSX.Element => {\n return (\n <div className={styles.loadingState} role=\"status\" aria-live=\"polite\">\n <Spinner size={18} ariaLabel={label} />\n <span className={styles.loadingLabel}>{label}</span>\n </div>\n );\n};\n\nexport const EntityIdPickerDialog = ({\n isOpen,\n entity,\n title,\n scope,\n onClose,\n onSelectId,\n}: EntityIdPickerDialogProps): JSX.Element | null => {\n const { t: tApp } = useTranslation();\n const { t } = useBackofficeReactTranslation();\n const [search, setSearch] = useState('');\n const [executedSearch, setExecutedSearch] = useState('');\n const [fetchKey, setFetchKey] = useState(0);\n const scrollRootRef = useRef<HTMLDivElement | null>(null);\n const entityState = useBackofficePickerEntityLoader(entity, {\n enabled: isOpen,\n });\n\n let pickerConfig: BackofficeRuntimeEntityPickerConfig | undefined;\n if (entityState.status === 'loaded') {\n pickerConfig = entityState.module.config.picker as\n BackofficeRuntimeEntityPickerConfig | undefined;\n }\n\n useEffect(() => {\n if (isOpen) {\n setSearch('');\n setExecutedSearch('');\n setFetchKey((value) => {\n return value + 1;\n });\n }\n }, [isOpen]);\n\n useEffect(() => {\n const timeout = setTimeout(() => {\n setExecutedSearch(search);\n }, 250);\n return () => {\n clearTimeout(timeout);\n };\n }, [search]);\n\n const footer = useMemo(() => {\n return buildFooter(onClose, t('common.actions.close'));\n }, [onClose, t]);\n\n const handleSearchChange = useCallback((next: string) => {\n setSearch(next);\n }, []);\n\n const handleSelectId = useCallback(\n (id: string) => {\n onSelectId(id);\n onClose();\n },\n [onClose, onSelectId],\n );\n\n if (!isOpen) {\n return null;\n }\n\n let resolvedSearchPlaceholder: string | undefined;\n if (pickerConfig?.searchPlaceholder != null) {\n resolvedSearchPlaceholder = resolveLabel(\n pickerConfig.searchPlaceholder,\n tApp,\n );\n }\n\n const trimmedSearch = executedSearch.trim();\n const isSearchRequired = pickerConfig?.searchRequired === true;\n\n let pickerNode: JSX.Element;\n if (entityState.status === 'loading') {\n pickerNode = <PickerLoading label={t('common.loading')} />;\n } else if (pickerConfig != null) {\n if (isSearchRequired && trimmedSearch === '') {\n pickerNode = <BackofficeEmptyState title={t('picker.searchRequired')} />;\n } else {\n pickerNode = (\n <PickerBody\n config={pickerConfig}\n entity={entity}\n search={executedSearch}\n scope={scope}\n fetchKey={fetchKey}\n resetKey={`${entity}:${trimmedSearch}:${JSON.stringify(scope ?? {})}:${fetchKey}`}\n scrollRootRef={scrollRootRef}\n labels={{\n loadMore: t('list.loadMore.action'),\n loading: t('list.loadMore.loading'),\n end: t('list.loadMore.end'),\n retry: t('common.actions.retry'),\n }}\n onSelectId={handleSelectId}\n />\n );\n }\n } else {\n pickerNode = (\n <PickerUnavailable message={t('picker.unavailable', { entity })} />\n );\n }\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n footer={footer}\n initialFocus=\"first-form-control\"\n scrollRootRef={scrollRootRef}\n >\n <BackofficePickerShell\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder={resolvedSearchPlaceholder}\n searchAriaLabel={resolvedSearchPlaceholder}\n searchName=\"backoffice-entity-picker-search\"\n searchEnabled={pickerConfig?.searchEnabled ?? true}\n >\n <BackofficeErrorBoundary\n fallback={(args: { error: unknown; reset: () => void }) => {\n const { reset } = args;\n return (\n <InlineBanner\n tone=\"danger\"\n title={t('picker.errors.loadFailed')}\n actions={\n <Button\n type=\"button\"\n variant=\"secondary\"\n onClick={() => {\n reset();\n setFetchKey((value) => {\n return value + 1;\n });\n }}\n >\n {t('common.actions.retry')}\n </Button>\n }\n >\n {null}\n </InlineBanner>\n );\n }}\n >\n <Suspense fallback={<PickerLoading label={t('common.loading')} />}>\n {pickerNode}\n </Suspense>\n </BackofficeErrorBoundary>\n </BackofficePickerShell>\n </Modal>\n );\n};\n\nexport type { BackofficePickerScope } from '@plumile/backoffice-core/types.js';\n\nexport default EntityIdPickerDialog;\n"],"mappings":"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"}