@isi-ui7/bos7-shared 0.2.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { c as Ke, g as Do } from "./index-CPY3DDQN.js";
2
- import { _ as Xa } from "./jspdf.es.min-Ds3Fsl60.js";
1
+ import { c as Ke, g as Do } from "./index-1k-PSj-b.js";
2
+ import { _ as Xa } from "./jspdf.es.min-CnDMxfNp.js";
3
3
  var vt = function(a) {
4
4
  return a && a.Math === Math && a;
5
5
  }, _ = (
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { m as s, a9 as o, A as t, Y as r, R as i, O as n, l, F as c, G as d, E as u, P as S, a3 as h, a2 as A, a1 as T, a0 as k, af as C, K as _, H as O, I as U, J as g, n as N, ah as P, ag as m, Q as E, i as F, D as p, f, y as I, S as R, j as x, V as v, a8 as y, o as L, h as b, L as B, M as D, T as M, W as H, C as V, e as W, z as q, a7 as K, a5 as w, a6 as z, v as G, aa as Q, ab as X, ac as Y, ad as j, k as J, U as Z, w as $, a4 as aa, Z as ea, _ as sa, p as oa, a as ta, N as ra, q as ia, r as na, $ as la, t as ca, ae as da, s as ua, B as Sa, x as ha, b as Aa, u as Ta, ai as ka, X as Ca, d as _a } from "./index-CPY3DDQN.js";
1
+ import { m as s, a9 as o, A as t, Y as r, R as i, O as n, l, F as c, G as d, E as u, P as S, a3 as h, a2 as A, a1 as T, a0 as k, af as C, K as _, H as O, I as U, J as g, n as N, ah as P, ag as m, Q as E, i as F, D as p, f, y as I, S as R, j as x, V as v, a8 as y, o as L, h as b, L as B, M as D, T as M, W as H, C as V, e as W, z as q, a7 as K, a5 as w, a6 as z, v as G, aa as Q, ab as X, ac as Y, ad as j, k as J, U as Z, w as $, a4 as aa, Z as ea, _ as sa, p as oa, a as ta, N as ra, q as ia, r as na, $ as la, t as ca, ae as da, s as ua, B as Sa, x as ha, b as Aa, u as Ta, ai as ka, X as Ca, d as _a } from "./index-1k-PSj-b.js";
2
2
  export {
3
3
  s as AUTH7_SCOPES,
4
4
  o as AppShellLayout,
@@ -9216,7 +9216,7 @@ function(n) {
9216
9216
  var d = h.getContext("2d");
9217
9217
  d.fillStyle = "#fff", d.fillRect(0, 0, h.width, h.height);
9218
9218
  var m = { ignoreMouse: !0, ignoreAnimation: !0, ignoreDimensions: !0 }, _ = this;
9219
- return (Kt.canvg ? Promise.resolve(Kt.canvg) : import("./index.es-Dy3lNA6h.js")).catch(function(k) {
9219
+ return (Kt.canvg ? Promise.resolve(Kt.canvg) : import("./index.es-zuTIhIvp.js")).catch(function(k) {
9220
9220
  return Promise.reject(new Error("Could not load canvg: " + k));
9221
9221
  }).then(function(k) {
9222
9222
  return k.default ? k.default : k;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isi-ui7/bos7-shared",
3
- "version": "0.2.13",
3
+ "version": "0.3.0",
4
4
  "description": "Shared auth7 and layout primitives for bos7 applications.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,6 +35,7 @@
35
35
  "./workflow/use-workflow-tracker": "./src/workflow/use-workflow-tracker.ts",
36
36
  "./workflow/crud-client": "./src/workflow/crud-client.ts",
37
37
  "./data-table/cursor-adapter": "./src/data-table/cursor-adapter.ts",
38
+ "./data-table/proxy": "./src/data-table/proxy.ts",
38
39
  "./notification/routing": "./src/notification/routing.ts",
39
40
  "./shell/interaction": "./src/shell/interaction.ts",
40
41
  "./shell/branch": "./src/shell/branch.ts",
@@ -69,14 +70,14 @@
69
70
  "peerDependencies": {
70
71
  "@carbon/icons-react": "^11.71.0",
71
72
  "@carbon/react": "^1.97.0",
72
- "@isi-ui7/corporate-themes": ">=0.2.0",
73
- "@isi-ui7/data-table": ">=0.2.0",
74
- "@isi-ui7/editable-table": ">=0.2.0",
75
- "@isi-ui7/i18n": ">=0.1.0",
76
- "@isi-ui7/lookup-input": ">=0.2.0",
73
+ "@isi-ui7/corporate-themes": ">=0.2.6",
74
+ "@isi-ui7/data-table": ">=0.2.4",
75
+ "@isi-ui7/editable-table": ">=0.2.3",
76
+ "@isi-ui7/i18n": ">=0.2.3",
77
+ "@isi-ui7/lookup-input": ">=0.2.4",
77
78
  "@isi-ui7/modal-manager": ">=0.2.0",
78
79
  "@isi-ui7/realtime": ">=0.2.0",
79
- "@isi-ui7/ui-shell": ">=0.2.0",
80
+ "@isi-ui7/ui-shell": ">=0.2.8",
80
81
  "next": "16.x",
81
82
  "react": ">=18",
82
83
  "react-dom": ">=18"
@@ -105,14 +106,14 @@
105
106
  "vite": "^5.0.0",
106
107
  "vite-plugin-dts": "^3.6.0",
107
108
  "vitest": "^1.0.0",
108
- "@isi-ui7/corporate-themes": "0.2.5",
109
+ "@isi-ui7/editable-table": "0.2.3",
110
+ "@isi-ui7/lookup-input": "0.2.4",
109
111
  "@isi-ui7/i18n": "0.2.3",
110
- "@isi-ui7/modal-manager": "0.2.2",
111
- "@isi-ui7/realtime": "0.2.3",
112
- "@isi-ui7/ui-shell": "0.2.8",
113
112
  "@isi-ui7/data-table": "0.2.4",
114
- "@isi-ui7/lookup-input": "0.2.4",
115
- "@isi-ui7/editable-table": "0.2.3"
113
+ "@isi-ui7/corporate-themes": "0.2.6",
114
+ "@isi-ui7/realtime": "0.2.3",
115
+ "@isi-ui7/modal-manager": "0.2.2",
116
+ "@isi-ui7/ui-shell": "0.2.8"
116
117
  },
117
118
  "dependencies": {
118
119
  "@carbon/icons-react": "^11.71.0",
@@ -125,9 +126,9 @@
125
126
  "jose": "^5.9.6",
126
127
  "jspdf": "^4.2.1",
127
128
  "jspdf-autotable": "^5.0.7",
128
- "@isi-ui7/lookup-input": "0.2.4",
129
+ "@isi-ui7/data-table": "0.2.4",
129
130
  "@isi-ui7/editable-table": "0.2.3",
130
- "@isi-ui7/data-table": "0.2.4"
131
+ "@isi-ui7/lookup-input": "0.2.4"
131
132
  },
132
133
  "scripts": {
133
134
  "build": "vite build",
@@ -12,6 +12,9 @@ import {
12
12
  ModalHeader,
13
13
  ProgressIndicator,
14
14
  ProgressStep,
15
+ Select,
16
+ SelectItem,
17
+ TextArea,
15
18
  ToastNotification,
16
19
  Toggle,
17
20
  } from "@carbon/react";
@@ -71,9 +74,19 @@ export function SharedCrudListPage({
71
74
  // deactivated / soft-deleted rows. Survives table remounts (tableKey).
72
75
  const [showInactive, setShowInactive] = useState(false);
73
76
  const inactiveParam = schema.includeInactiveParam ?? "include_inactive";
74
- const additionalParams = schema.showInactiveToggle
75
- ? { [inactiveParam]: showInactive ? "true" : "" }
76
- : undefined;
77
+ // Single-select toolbar filter (opt-in via schema.filterSelect) — same
78
+ // additionalParams wiring as showInactiveToggle, for an arbitrary enum
79
+ // column instead of a boolean.
80
+ const [filterValue, setFilterValue] = useState("");
81
+ const hasInactiveToggle = Boolean(schema.showInactiveToggle);
82
+ const hasFilterSelect = Boolean(schema.filterSelect);
83
+ const additionalParams =
84
+ hasInactiveToggle || hasFilterSelect
85
+ ? {
86
+ ...(hasInactiveToggle ? { [inactiveParam]: showInactive ? "true" : "" } : {}),
87
+ ...(hasFilterSelect ? { [schema.filterSelect!.param]: filterValue } : {}),
88
+ }
89
+ : undefined;
77
90
  return (
78
91
  <PageLayout>
79
92
  <PageHeader title={schema.title} subtitle={schema.subtitle} />
@@ -104,6 +117,23 @@ export function SharedCrudListPage({
104
117
  onToggle={(checked) => setShowInactive(checked)}
105
118
  />
106
119
  ) : null}
120
+ {schema.filterSelect ? (
121
+ <div style={{ minWidth: "10rem" }}>
122
+ <Select
123
+ id={`filter-select-${schema.apiPath}`}
124
+ labelText=""
125
+ hideLabel
126
+ size="sm"
127
+ value={filterValue}
128
+ onChange={(e) => setFilterValue(e.target.value)}
129
+ >
130
+ <SelectItem value="" text={schema.filterSelect.allLabel ?? "Semua"} />
131
+ {schema.filterSelect.options.map((opt) => (
132
+ <SelectItem key={opt.value} value={opt.value} text={opt.label} />
133
+ ))}
134
+ </Select>
135
+ </div>
136
+ ) : null}
107
137
  {toolbarExtra}
108
138
  <Button renderIcon={Add} size="sm" onClick={onAdd}>
109
139
  {schema.addButtonLabel || labels.next}
@@ -154,6 +184,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
154
184
  const [customActionRow, setCustomActionRow] = useState<Record<string, unknown> | null>(null);
155
185
  const [customActioning, setCustomActioning] = useState(false);
156
186
  const [customActionError, setCustomActionError] = useState<string | null>(null);
187
+ const [customActionReason, setCustomActionReason] = useState("");
157
188
 
158
189
  // Workflow completion toast
159
190
  const [wfToast, setWfToast] = useState<{ kind: 'success' | 'error'; title: string; subtitle?: string } | null>(null);
@@ -192,6 +223,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
192
223
  setCustomActionId(actionId);
193
224
  setCustomActionRow(row);
194
225
  setCustomActionError(null);
226
+ setCustomActionReason("");
195
227
  return;
196
228
  }
197
229
 
@@ -224,6 +256,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
224
256
  if (!schema) return;
225
257
  const id = String(customActionRow.id ?? "");
226
258
  const path = typeof schema.apiPath === "function" ? schema.apiPath(id) : schema.apiPath;
259
+ const body = schema.reasonRequired ? { reason: customActionReason.trim() } : {};
227
260
  setCustomActioning(true);
228
261
  setCustomActionError(null);
229
262
  try {
@@ -232,17 +265,17 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
232
265
  const res = await fetch(path, {
233
266
  method: schema.method ?? "POST",
234
267
  headers: { "Content-Type": "application/json" },
235
- body: JSON.stringify({}),
268
+ body: JSON.stringify(body),
236
269
  });
237
270
  if (!res.ok) {
238
- const body = (await res.json().catch(() => ({}))) as { error?: string };
239
- throw new Error(body.error ?? `Gagal (HTTP ${res.status})`);
271
+ const resBody = (await res.json().catch(() => ({}))) as { error?: string };
272
+ throw new Error(resBody.error ?? `Gagal (HTTP ${res.status})`);
240
273
  }
241
274
  setCustomActionId(null);
242
275
  setCustomActionRow(null);
243
276
  setTableKey((v) => v + 1);
244
277
  } else {
245
- const instanceId = await submitWorkflowForm(path, schema.method ?? "POST", {});
278
+ const instanceId = await submitWorkflowForm(path, schema.method ?? "POST", body);
246
279
  setCustomActionId(null);
247
280
  setCustomActionRow(null);
248
281
  setTableKey((v) => v + 1);
@@ -253,7 +286,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
253
286
  } finally {
254
287
  setCustomActioning(false);
255
288
  }
256
- }, [customActionId, customActionRow, listSchema.customActionSchemas, tracker]);
289
+ }, [customActionId, customActionRow, customActionReason, listSchema.customActionSchemas, tracker]);
257
290
 
258
291
  const activeCustomSchema =
259
292
  customActionId ? listSchema.customActionSchemas?.[customActionId] : undefined;
@@ -262,6 +295,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
262
295
  ? activeCustomSchema.description(customActionRow ?? {})
263
296
  : activeCustomSchema.description)
264
297
  : "";
298
+ const customReasonMissing = Boolean(activeCustomSchema?.reasonRequired) && customActionReason.trim().length === 0;
265
299
 
266
300
  return (
267
301
  <>
@@ -308,6 +342,15 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
308
342
  <ModalBody hasScrollingContent={false}>
309
343
  <div style={{ display: "grid", gap: "0.5rem", fontSize: "0.875rem" }}>
310
344
  <p>{customDescription}</p>
345
+ {activeCustomSchema.reasonRequired ? (
346
+ <TextArea
347
+ labelText={activeCustomSchema.reasonLabel ?? "Alasan"}
348
+ value={customActionReason}
349
+ onChange={(e) => setCustomActionReason(e.target.value)}
350
+ disabled={customActioning}
351
+ rows={3}
352
+ />
353
+ ) : null}
311
354
  {customActionError ? (
312
355
  <InlineNotification
313
356
  kind="error"
@@ -329,7 +372,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
329
372
  <Button
330
373
  kind="primary"
331
374
  onClick={() => void doCustomAction()}
332
- disabled={customActioning || !customActionRow}
375
+ disabled={customActioning || !customActionRow || customReasonMissing}
333
376
  >
334
377
  {customActioning ? labels.saving : (activeCustomSchema.confirmLabel ?? labels.next)}
335
378
  </Button>
package/src/crud-types.ts CHANGED
@@ -37,6 +37,16 @@ export type CrudCustomActionSchema = {
37
37
  * Dipakai untuk aksi operasional seperti employee terminate/reactivate.
38
38
  */
39
39
  direct?: boolean;
40
+ /**
41
+ * Tampilkan textarea alasan di modal konfirmasi dan wajibkan isi non-kosong
42
+ * sebelum tombol confirm aktif. Nilai dikirim sebagai `{ reason }` di body
43
+ * request (digabung ke payload lain bila apiPath sama dipakai untuk field
44
+ * lain). Dipakai untuk aksi yang butuh audit trail eksplisit (mis. Set
45
+ * Non-Aktif produk) — lihat appdefs/{module}/index.def `withReason: true`.
46
+ */
47
+ reasonRequired?: boolean;
48
+ /** Label di atas textarea alasan. Default "Alasan". */
49
+ reasonLabel?: string;
40
50
  };
41
51
 
42
52
  export type CrudListSchema = {
@@ -76,6 +86,22 @@ export type CrudListSchema = {
76
86
  * or onCustomAction).
77
87
  */
78
88
  popupMenuItemsForRow?: (row: Record<string, unknown>) => CrudPopupMenuItem[];
89
+ /**
90
+ * Optional single-select toolbar filter. When an option other than "(all)"
91
+ * is picked, the list request carries `?<param>=<value>`; picking back to
92
+ * "(all)" removes the param. Mirrors `showInactiveToggle`'s additionalParams
93
+ * wiring but for an arbitrary enum column (e.g. `vendor_type`). Additive +
94
+ * backward-compatible: omit it and the toolbar renders exactly as before.
95
+ */
96
+ filterSelect?: {
97
+ /** Query param name the backend reads (e.g. "vendor_type"). */
98
+ param: string;
99
+ /** Toolbar label for the select. */
100
+ label: string;
101
+ options: Array<{ value: string; label: string }>;
102
+ /** Label for the "no filter" option. Default "Semua". */
103
+ allLabel?: string;
104
+ };
79
105
  };
80
106
 
81
107
  export type CrudDeleteSchema<TData extends Record<string, unknown>> = {
@@ -10,6 +10,15 @@ export interface ProxyBackendPostOptions {
10
10
  accessTokenCookie?: string;
11
11
  }
12
12
 
13
+ // Toolbar filters (showInactiveToggle, filterSelect) ride as query-string
14
+ // params on the incoming BFF request (see ServerDataTable's additionalParams)
15
+ // even though the body carries the DataTable sort/search/pagination payload.
16
+ // Forward that query string to the backend or those filters silently no-op.
17
+ function backendPathWithQuery(req: NextRequest, path: string): string {
18
+ const search = new URL(req.url).search;
19
+ return search ? `${path}${search}` : path;
20
+ }
21
+
13
22
  export async function proxyBackendPost(
14
23
  req: NextRequest,
15
24
  opts: ProxyBackendPostOptions,
@@ -17,7 +26,7 @@ export async function proxyBackendPost(
17
26
  const accessToken = req.cookies.get(opts.accessTokenCookie ?? "access_token")?.value;
18
27
  const result = await exchangeUserToken(accessToken ?? "", opts.audience);
19
28
  const body = await req.json();
20
- const res = await fetch(`${opts.backendUrl}${opts.path}`, {
29
+ const res = await fetch(`${opts.backendUrl}${backendPathWithQuery(req, opts.path)}`, {
21
30
  method: "POST",
22
31
  headers: {
23
32
  "Content-Type": "application/json",
@@ -57,7 +66,7 @@ export async function proxyQueryRoute(
57
66
  const accessToken = req.cookies.get(opts.accessTokenCookie ?? "access_token")?.value;
58
67
  const result = await exchangeUserToken(accessToken ?? "", opts.audience);
59
68
  const body = await req.json();
60
- const res = await fetch(`${opts.backendUrl}${opts.path}`, {
69
+ const res = await fetch(`${opts.backendUrl}${backendPathWithQuery(req, opts.path)}`, {
61
70
  method: "POST",
62
71
  headers: {
63
72
  "Content-Type": "application/json",