@isi-ui7/bos7-shared 0.2.12 → 0.2.13
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/crud-components.d.ts +10 -2
- package/dist/crud-types.d.ts +19 -0
- package/dist/export.d.ts +22 -1
- package/dist/i18n.d.ts +1 -0
- package/dist/index-CPY3DDQN.js +28761 -0
- package/dist/{index.es-Bylk5fh-.js → index.es-Dy3lNA6h.js} +4 -7
- package/dist/index.js +72 -21147
- package/dist/{jspdf.es.min-Dzm5j8wC.js → jspdf.es.min-Ds3Fsl60.js} +1 -1
- package/package.json +46 -95
- package/src/crud-components.tsx +45 -5
- package/src/crud-types.ts +19 -0
- package/src/export.ts +144 -19
- package/src/i18n.tsx +2 -0
|
@@ -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-
|
|
9219
|
+
return (Kt.canvg ? Promise.resolve(Kt.canvg) : import("./index.es-Dy3lNA6h.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.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Shared auth7 and layout primitives for bos7 applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"directory": "packages/bos7-shared"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
|
-
"main": "
|
|
13
|
-
"module": "
|
|
14
|
-
"types": "
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"module": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
15
|
"exports": {
|
|
16
|
-
".": "./
|
|
16
|
+
".": "./dist/index.js",
|
|
17
17
|
"./auth7": "./src/auth7/index.ts",
|
|
18
18
|
"./page-layout": "./src/page-layout.tsx",
|
|
19
19
|
"./modal-layout": "./src/modal-layout.tsx",
|
|
@@ -49,139 +49,90 @@
|
|
|
49
49
|
"./bff/health": "./src/bff/health.ts",
|
|
50
50
|
"./observability/otel-init": "./src/observability/otel-init.ts",
|
|
51
51
|
"./form-types": "./src/form-types.ts",
|
|
52
|
-
"./form-value-schema": "./src/form-value-schema.ts",
|
|
53
52
|
"./form-numeric": "./src/form-numeric.ts",
|
|
54
53
|
"./form-renderer": "./src/form-renderer.tsx",
|
|
55
54
|
"./form-renderer-utils": "./src/form-renderer-utils.ts",
|
|
56
55
|
"./form-rules": "./src/form-rules.ts",
|
|
57
56
|
"./i18n": "./src/i18n.tsx",
|
|
58
57
|
"./workflow/use-crud-form": "./src/workflow/use-crud-form.ts",
|
|
59
|
-
"./styles": "./src/form-contract.css"
|
|
58
|
+
"./styles": "./src/form-contract.css",
|
|
59
|
+
"./form-value-schema": "./src/form-value-schema.ts"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"registry": "https://registry.npmjs.org",
|
|
63
|
-
"access": "public"
|
|
64
|
-
"main": "dist/index.js",
|
|
65
|
-
"module": "dist/index.js",
|
|
66
|
-
"types": "dist/index.d.ts",
|
|
67
|
-
"exports": {
|
|
68
|
-
".": "./dist/index.js",
|
|
69
|
-
"./auth7": "./src/auth7/index.ts",
|
|
70
|
-
"./page-layout": "./src/page-layout.tsx",
|
|
71
|
-
"./modal-layout": "./src/modal-layout.tsx",
|
|
72
|
-
"./form-layout": "./src/form-layout.tsx",
|
|
73
|
-
"./backend": "./src/backend.ts",
|
|
74
|
-
"./export": "./src/export.ts",
|
|
75
|
-
"./types/notification": "./src/types/notification.ts",
|
|
76
|
-
"./nats/client": "./src/nats/client.ts",
|
|
77
|
-
"./event-bus": "./src/event-bus/index.ts",
|
|
78
|
-
"./event-bus/useEventBus": "./src/event-bus/useEventBus.ts",
|
|
79
|
-
"./api": "./src/api.ts",
|
|
80
|
-
"./types/core": "./src/types/core.ts",
|
|
81
|
-
"./style-contract": "./src/style-contract.ts",
|
|
82
|
-
"./crud-components": "./src/crud-components.tsx",
|
|
83
|
-
"./workflow/backend": "./src/workflow/backend.ts",
|
|
84
|
-
"./workflow/notif7": "./src/workflow/notif7.ts",
|
|
85
|
-
"./workflow/api-client": "./src/workflow/api-client.ts",
|
|
86
|
-
"./workflow/starter": "./src/workflow/starter.ts",
|
|
87
|
-
"./workflow/use-workflow-tracker": "./src/workflow/use-workflow-tracker.ts",
|
|
88
|
-
"./workflow/crud-client": "./src/workflow/crud-client.ts",
|
|
89
|
-
"./data-table/cursor-adapter": "./src/data-table/cursor-adapter.ts",
|
|
90
|
-
"./notification/routing": "./src/notification/routing.ts",
|
|
91
|
-
"./shell/interaction": "./src/shell/interaction.ts",
|
|
92
|
-
"./shell/branch": "./src/shell/branch.ts",
|
|
93
|
-
"./notif7": "./src/notif7.ts",
|
|
94
|
-
"./shell/provider": "./src/shell/provider.tsx",
|
|
95
|
-
"./shell/app-shell-layout": "./src/shell/app-shell-layout.tsx",
|
|
96
|
-
"./notifications-bff": "./src/notifications-bff.ts",
|
|
97
|
-
"./bff/auth-me": "./src/bff/auth-me.ts",
|
|
98
|
-
"./bff/auth-refresh": "./src/bff/auth-refresh.ts",
|
|
99
|
-
"./bff/auth-logout": "./src/bff/auth-logout.ts",
|
|
100
|
-
"./bff/auth-preferences": "./src/bff/auth-preferences.ts",
|
|
101
|
-
"./bff/health": "./src/bff/health.ts",
|
|
102
|
-
"./observability/otel-init": "./src/observability/otel-init.ts",
|
|
103
|
-
"./form-types": "./src/form-types.ts",
|
|
104
|
-
"./form-numeric": "./src/form-numeric.ts",
|
|
105
|
-
"./form-renderer": "./src/form-renderer.tsx",
|
|
106
|
-
"./form-renderer-utils": "./src/form-renderer-utils.ts",
|
|
107
|
-
"./form-rules": "./src/form-rules.ts",
|
|
108
|
-
"./i18n": "./src/i18n.tsx",
|
|
109
|
-
"./workflow/use-crud-form": "./src/workflow/use-crud-form.ts",
|
|
110
|
-
"./styles": "./src/form-contract.css",
|
|
111
|
-
"./form-value-schema": "./src/form-value-schema.ts"
|
|
112
|
-
}
|
|
63
|
+
"access": "public"
|
|
113
64
|
},
|
|
114
65
|
"files": [
|
|
115
66
|
"dist",
|
|
116
67
|
"src"
|
|
117
68
|
],
|
|
118
|
-
"scripts": {
|
|
119
|
-
"build": "vite build",
|
|
120
|
-
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
121
|
-
"test": "vitest run --passWithNoTests",
|
|
122
|
-
"typecheck": "tsc --noEmit"
|
|
123
|
-
},
|
|
124
69
|
"peerDependencies": {
|
|
125
|
-
"react": "
|
|
126
|
-
"react
|
|
127
|
-
"next": "16.x",
|
|
70
|
+
"@carbon/icons-react": "^11.71.0",
|
|
71
|
+
"@carbon/react": "^1.97.0",
|
|
128
72
|
"@isi-ui7/corporate-themes": ">=0.2.0",
|
|
73
|
+
"@isi-ui7/data-table": ">=0.2.0",
|
|
74
|
+
"@isi-ui7/editable-table": ">=0.2.0",
|
|
129
75
|
"@isi-ui7/i18n": ">=0.1.0",
|
|
130
|
-
"@isi-ui7/
|
|
76
|
+
"@isi-ui7/lookup-input": ">=0.2.0",
|
|
131
77
|
"@isi-ui7/modal-manager": ">=0.2.0",
|
|
132
78
|
"@isi-ui7/realtime": ">=0.2.0",
|
|
133
|
-
"@
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"@isi-ui7/editable-table": ">=0.2.0"
|
|
79
|
+
"@isi-ui7/ui-shell": ">=0.2.0",
|
|
80
|
+
"next": "16.x",
|
|
81
|
+
"react": ">=18",
|
|
82
|
+
"react-dom": ">=18"
|
|
138
83
|
},
|
|
139
84
|
"devDependencies": {
|
|
85
|
+
"@carbon/icons-react": "^11.71.0",
|
|
86
|
+
"@carbon/react": "^1.97.0",
|
|
140
87
|
"@eslint/eslintrc": "^3.3.1",
|
|
141
88
|
"@eslint/js": "^9.39.0",
|
|
89
|
+
"@testing-library/dom": "^10.0.0",
|
|
90
|
+
"@testing-library/react": "^16.0.0",
|
|
91
|
+
"@testing-library/user-event": "^14.5.0",
|
|
142
92
|
"@types/react": "^19.0.0",
|
|
143
93
|
"@types/react-dom": "^19.0.0",
|
|
144
94
|
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
145
95
|
"@typescript-eslint/parser": "^8.52.0",
|
|
146
96
|
"@vitejs/plugin-react": "^4.2.0",
|
|
147
|
-
"@testing-library/dom": "^10.0.0",
|
|
148
|
-
"@testing-library/react": "^16.0.0",
|
|
149
|
-
"@testing-library/user-event": "^14.5.0",
|
|
150
97
|
"eslint": "9.39.2",
|
|
151
98
|
"eslint-config-prettier": "^9.1.0",
|
|
152
99
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
|
153
100
|
"eslint-plugin-react": "^7.33.2",
|
|
154
101
|
"eslint-plugin-react-hooks": "^4.6.2",
|
|
155
102
|
"jsdom": "^22.1.0",
|
|
103
|
+
"next": "16.1.1",
|
|
156
104
|
"typescript": "^5.3.3",
|
|
157
105
|
"vite": "^5.0.0",
|
|
158
106
|
"vite-plugin-dts": "^3.6.0",
|
|
159
107
|
"vitest": "^1.0.0",
|
|
160
|
-
"
|
|
161
|
-
"@isi-ui7/
|
|
162
|
-
"@isi-ui7/
|
|
163
|
-
"@isi-ui7/
|
|
164
|
-
"@isi-ui7/
|
|
165
|
-
"@isi-ui7/
|
|
166
|
-
"@
|
|
167
|
-
"@
|
|
168
|
-
"@isi-ui7/data-table": "workspace:*",
|
|
169
|
-
"@isi-ui7/lookup-input": "workspace:*",
|
|
170
|
-
"@isi-ui7/editable-table": "workspace:*"
|
|
108
|
+
"@isi-ui7/corporate-themes": "0.2.5",
|
|
109
|
+
"@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
|
+
"@isi-ui7/data-table": "0.2.4",
|
|
114
|
+
"@isi-ui7/lookup-input": "0.2.4",
|
|
115
|
+
"@isi-ui7/editable-table": "0.2.3"
|
|
171
116
|
},
|
|
172
117
|
"dependencies": {
|
|
173
118
|
"@carbon/icons-react": "^11.71.0",
|
|
174
119
|
"@carbon/react": "^1.97.0",
|
|
175
|
-
"@
|
|
176
|
-
"@
|
|
177
|
-
"@
|
|
120
|
+
"@opentelemetry/auto-instrumentations-node": "^0.57.0",
|
|
121
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
|
|
122
|
+
"@opentelemetry/sdk-node": "^0.57.0",
|
|
123
|
+
"axios": "^1.7.7",
|
|
124
|
+
"exceljs": "^4.4.0",
|
|
178
125
|
"jose": "^5.9.6",
|
|
179
|
-
"xlsx": "^0.18.5",
|
|
180
126
|
"jspdf": "^4.2.1",
|
|
181
127
|
"jspdf-autotable": "^5.0.7",
|
|
182
|
-
"
|
|
183
|
-
"@
|
|
184
|
-
"@
|
|
185
|
-
|
|
128
|
+
"@isi-ui7/lookup-input": "0.2.4",
|
|
129
|
+
"@isi-ui7/editable-table": "0.2.3",
|
|
130
|
+
"@isi-ui7/data-table": "0.2.4"
|
|
131
|
+
},
|
|
132
|
+
"scripts": {
|
|
133
|
+
"build": "vite build",
|
|
134
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
135
|
+
"test": "vitest run --passWithNoTests",
|
|
136
|
+
"typecheck": "tsc --noEmit"
|
|
186
137
|
}
|
|
187
|
-
}
|
|
138
|
+
}
|
package/src/crud-components.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ProgressIndicator,
|
|
14
14
|
ProgressStep,
|
|
15
15
|
ToastNotification,
|
|
16
|
+
Toggle,
|
|
16
17
|
} from "@carbon/react";
|
|
17
18
|
import { ServerDataTable } from "@isi-ui7/data-table";
|
|
18
19
|
import type { I_DataTblColumn } from "@isi-ui7/data-table";
|
|
@@ -50,14 +51,29 @@ export function SharedCrudListPage({
|
|
|
50
51
|
schema,
|
|
51
52
|
tableKey,
|
|
52
53
|
onAdd,
|
|
53
|
-
onPopupClick
|
|
54
|
+
onPopupClick,
|
|
55
|
+
toolbarExtra,
|
|
54
56
|
}: {
|
|
55
57
|
schema: CrudListSchema;
|
|
56
58
|
tableKey?: number;
|
|
57
59
|
onAdd: () => void;
|
|
58
60
|
onPopupClick: (row: Record<string, unknown>, actionId: string) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Optional extra toolbar action(s) rendered to the LEFT of the built-in Add
|
|
63
|
+
* button (e.g. a secondary "Bulk Create" button). Additive + backward-compatible:
|
|
64
|
+
* omit it and the toolbar renders exactly as before.
|
|
65
|
+
*/
|
|
66
|
+
toolbarExtra?: ReactNode;
|
|
59
67
|
}) {
|
|
60
68
|
const labels = useBosSharedI18n();
|
|
69
|
+
// "Show Inactive" toggle state (opt-in via schema.showInactiveToggle). When
|
|
70
|
+
// on, the list request carries ?<param>=true so the backend surfaces
|
|
71
|
+
// deactivated / soft-deleted rows. Survives table remounts (tableKey).
|
|
72
|
+
const [showInactive, setShowInactive] = useState(false);
|
|
73
|
+
const inactiveParam = schema.includeInactiveParam ?? "include_inactive";
|
|
74
|
+
const additionalParams = schema.showInactiveToggle
|
|
75
|
+
? { [inactiveParam]: showInactive ? "true" : "" }
|
|
76
|
+
: undefined;
|
|
61
77
|
return (
|
|
62
78
|
<PageLayout>
|
|
63
79
|
<PageHeader title={schema.title} subtitle={schema.subtitle} />
|
|
@@ -65,14 +81,34 @@ export function SharedCrudListPage({
|
|
|
65
81
|
<ServerDataTable
|
|
66
82
|
key={tableKey}
|
|
67
83
|
apiPath={schema.apiPath}
|
|
84
|
+
additionalParams={additionalParams}
|
|
68
85
|
columns={schema.columns as Record<string, I_DataTblColumn>}
|
|
69
86
|
showSearch={schema.showSearch ?? true}
|
|
70
87
|
showSort={schema.showSort ?? true}
|
|
71
|
-
popupMenu={{
|
|
88
|
+
popupMenu={{
|
|
89
|
+
src: "local",
|
|
90
|
+
items: schema.popupMenuItems,
|
|
91
|
+
itemsForRow: schema.popupMenuItemsForRow,
|
|
92
|
+
onClick: onPopupClick,
|
|
93
|
+
}}
|
|
72
94
|
toolbarActions={
|
|
73
|
-
<
|
|
74
|
-
{schema.
|
|
75
|
-
|
|
95
|
+
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
|
96
|
+
{schema.showInactiveToggle ? (
|
|
97
|
+
<Toggle
|
|
98
|
+
id={`show-inactive-${schema.apiPath}`}
|
|
99
|
+
size="sm"
|
|
100
|
+
labelText=""
|
|
101
|
+
labelA={schema.showInactiveLabel ?? labels.showInactive}
|
|
102
|
+
labelB={schema.showInactiveLabel ?? labels.showInactive}
|
|
103
|
+
toggled={showInactive}
|
|
104
|
+
onToggle={(checked) => setShowInactive(checked)}
|
|
105
|
+
/>
|
|
106
|
+
) : null}
|
|
107
|
+
{toolbarExtra}
|
|
108
|
+
<Button renderIcon={Add} size="sm" onClick={onAdd}>
|
|
109
|
+
{schema.addButtonLabel || labels.next}
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
76
112
|
}
|
|
77
113
|
/>
|
|
78
114
|
</PageBody>
|
|
@@ -92,6 +128,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
|
|
|
92
128
|
onEdit,
|
|
93
129
|
deleteApiPath,
|
|
94
130
|
onCustomAction,
|
|
131
|
+
toolbarExtra,
|
|
95
132
|
}: {
|
|
96
133
|
listSchema: CrudListSchema;
|
|
97
134
|
deleteSchema?: CrudDeleteSchema<TData>;
|
|
@@ -99,6 +136,8 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
|
|
|
99
136
|
onView?: (id: string) => void;
|
|
100
137
|
onEdit?: (id: string) => void;
|
|
101
138
|
deleteApiPath?: (id: string) => string;
|
|
139
|
+
/** Optional extra toolbar action(s) rendered beside the built-in Add button. */
|
|
140
|
+
toolbarExtra?: ReactNode;
|
|
102
141
|
/**
|
|
103
142
|
* Level 1: called for any popup action id not handled by built-in logic
|
|
104
143
|
* (view / edit / delete) and not in listSchema.customActionSchemas (Level 2).
|
|
@@ -231,6 +270,7 @@ export function CrudSchemaListPage<TData extends Record<string, unknown>>({
|
|
|
231
270
|
tableKey={tableKey}
|
|
232
271
|
onAdd={onAdd}
|
|
233
272
|
onPopupClick={onPopupClick}
|
|
273
|
+
toolbarExtra={toolbarExtra}
|
|
234
274
|
/>
|
|
235
275
|
|
|
236
276
|
{wfToast ? (
|
package/src/crud-types.ts
CHANGED
|
@@ -57,6 +57,25 @@ export type CrudListSchema = {
|
|
|
57
57
|
* Keyed by popup menu item id. Actions not listed here fall through to onCustomAction (Level 1).
|
|
58
58
|
*/
|
|
59
59
|
customActionSchemas?: Record<string, CrudCustomActionSchema>;
|
|
60
|
+
/**
|
|
61
|
+
* Show a "Show Inactive" toolbar toggle. When on, the list request carries
|
|
62
|
+
* `?<includeInactiveParam>=true` so the backend surfaces soft-deleted /
|
|
63
|
+
* deactivated rows too. Default off keeps the list active-only. Pair with
|
|
64
|
+
* `popupMenuItemsForRow` so inactive rows expose "Reactivate".
|
|
65
|
+
*/
|
|
66
|
+
showInactiveToggle?: boolean;
|
|
67
|
+
/** Query param name toggled by the Show-Inactive control. Default "include_inactive". */
|
|
68
|
+
includeInactiveParam?: string;
|
|
69
|
+
/** Label for the Show-Inactive toggle (i18n'd by the caller). Default "Tampilkan Nonaktif". */
|
|
70
|
+
showInactiveLabel?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Per-row popup items. When provided, overrides `popupMenuItems` for each row
|
|
73
|
+
* so the menu can depend on row state — e.g. active rows show "Deactivate",
|
|
74
|
+
* inactive rows show "Reactivate". Returned item ids route the same way as
|
|
75
|
+
* `popupMenuItems` (view/edit/delete built-in; others via customActionSchemas
|
|
76
|
+
* or onCustomAction).
|
|
77
|
+
*/
|
|
78
|
+
popupMenuItemsForRow?: (row: Record<string, unknown>) => CrudPopupMenuItem[];
|
|
60
79
|
};
|
|
61
80
|
|
|
62
81
|
export type CrudDeleteSchema<TData extends Record<string, unknown>> = {
|
package/src/export.ts
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
// lib/export.ts — utility export Excel dan PDF untuk
|
|
1
|
+
// lib/export.ts — utility export Excel (styled, via exceljs) dan PDF untuk
|
|
2
|
+
// halaman laporan (DG-02 PM review: export mentah -> format cantik).
|
|
2
3
|
|
|
3
|
-
import
|
|
4
|
+
import ExcelJS from 'exceljs';
|
|
4
5
|
|
|
5
6
|
export interface ExportColumn {
|
|
6
7
|
field: string;
|
|
7
8
|
title: string;
|
|
8
9
|
displayFormat?: string;
|
|
10
|
+
/** Cell alignment. Default: 'right' for displayFormat="currency", else 'left'. */
|
|
11
|
+
align?: 'left' | 'center' | 'right';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Header block rendered above the column headers (report title/period/applied filters). */
|
|
15
|
+
export interface ExportMeta {
|
|
16
|
+
title?: string;
|
|
17
|
+
/** e.g. "01 Jul 2026 – 24 Jul 2026" or "Per 24 Jul 2026". */
|
|
18
|
+
period?: string;
|
|
19
|
+
/** Applied filter values, e.g. [{label:"Cabang", value:"001 — Pusat"}]. Omitted/empty filters should not be passed in. */
|
|
20
|
+
filters?: Array<{ label: string; value: string }>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Values for a highlighted total/subtotal row, keyed by column `field`. Fields absent here render blank in the total row. */
|
|
24
|
+
export type ExportTotals = Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
export interface ExportOptions {
|
|
27
|
+
meta?: ExportMeta;
|
|
28
|
+
totals?: ExportTotals;
|
|
29
|
+
/** Label shown in the total row's leading cell(s). Default: "Total". */
|
|
30
|
+
totalsLabel?: string;
|
|
9
31
|
}
|
|
10
32
|
|
|
11
33
|
function cellValue(val: unknown, format?: string): unknown {
|
|
@@ -15,20 +37,128 @@ function cellValue(val: unknown, format?: string): unknown {
|
|
|
15
37
|
return val;
|
|
16
38
|
}
|
|
17
39
|
|
|
18
|
-
|
|
40
|
+
function columnAlign(c: ExportColumn): 'left' | 'center' | 'right' {
|
|
41
|
+
return c.align ?? (c.displayFormat === 'currency' ? 'right' : 'left');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const THIN_BORDER: Partial<ExcelJS.Borders> = {
|
|
45
|
+
top: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
|
46
|
+
left: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
|
47
|
+
bottom: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
|
48
|
+
right: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
|
49
|
+
};
|
|
50
|
+
const HEADER_FILL: ExcelJS.Fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF1F61FE' } };
|
|
51
|
+
const TOTAL_FILL: ExcelJS.Fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE8F0FE' } };
|
|
52
|
+
|
|
53
|
+
export async function exportToExcel<T extends Record<string, unknown>>(
|
|
19
54
|
data: T[],
|
|
20
55
|
columns: ExportColumn[],
|
|
21
|
-
sheetName: string
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
56
|
+
sheetName: string,
|
|
57
|
+
options?: ExportOptions,
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const wb = new ExcelJS.Workbook();
|
|
60
|
+
const ws = wb.addWorksheet(sheetName.slice(0, 31));
|
|
61
|
+
const colCount = columns.length;
|
|
62
|
+
|
|
63
|
+
let cursorRow = 1;
|
|
64
|
+
const meta = options?.meta;
|
|
65
|
+
if (meta?.title) {
|
|
66
|
+
const row = ws.getRow(cursorRow);
|
|
67
|
+
row.getCell(1).value = meta.title;
|
|
68
|
+
row.getCell(1).font = { bold: true, size: 14 };
|
|
69
|
+
ws.mergeCells(cursorRow, 1, cursorRow, colCount);
|
|
70
|
+
cursorRow += 1;
|
|
71
|
+
}
|
|
72
|
+
if (meta?.period) {
|
|
73
|
+
const row = ws.getRow(cursorRow);
|
|
74
|
+
row.getCell(1).value = `Periode: ${meta.period}`;
|
|
75
|
+
row.getCell(1).font = { italic: true, color: { argb: 'FF525252' } };
|
|
76
|
+
ws.mergeCells(cursorRow, 1, cursorRow, colCount);
|
|
77
|
+
cursorRow += 1;
|
|
78
|
+
}
|
|
79
|
+
if (meta?.filters?.length) {
|
|
80
|
+
const row = ws.getRow(cursorRow);
|
|
81
|
+
row.getCell(1).value = meta.filters.map((f) => `${f.label}: ${f.value}`).join(' · ');
|
|
82
|
+
row.getCell(1).font = { italic: true, size: 10, color: { argb: 'FF525252' } };
|
|
83
|
+
ws.mergeCells(cursorRow, 1, cursorRow, colCount);
|
|
84
|
+
cursorRow += 1;
|
|
85
|
+
}
|
|
86
|
+
if (meta?.title || meta?.period || meta?.filters?.length) {
|
|
87
|
+
cursorRow += 1; // blank spacer row before the column headers
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const headerRowIndex = cursorRow;
|
|
91
|
+
const headerRow = ws.getRow(headerRowIndex);
|
|
92
|
+
columns.forEach((c, i) => {
|
|
93
|
+
const cell = headerRow.getCell(i + 1);
|
|
94
|
+
cell.value = c.title;
|
|
95
|
+
cell.font = { bold: true, color: { argb: 'FFFFFFFF' } };
|
|
96
|
+
cell.fill = HEADER_FILL;
|
|
97
|
+
cell.border = THIN_BORDER;
|
|
98
|
+
cell.alignment = { horizontal: columnAlign(c), vertical: 'middle' };
|
|
99
|
+
});
|
|
100
|
+
headerRow.height = 20;
|
|
101
|
+
cursorRow += 1;
|
|
102
|
+
|
|
103
|
+
data.forEach((row) => {
|
|
104
|
+
const r = ws.getRow(cursorRow);
|
|
105
|
+
columns.forEach((c, i) => {
|
|
106
|
+
const cell = r.getCell(i + 1);
|
|
107
|
+
cell.value = cellValue(row[c.field], c.displayFormat) as ExcelJS.CellValue;
|
|
108
|
+
cell.border = THIN_BORDER;
|
|
109
|
+
cell.alignment = { horizontal: columnAlign(c), vertical: 'middle' };
|
|
110
|
+
if (c.displayFormat === 'currency') cell.numFmt = '#,##0.00';
|
|
111
|
+
});
|
|
112
|
+
cursorRow += 1;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (options?.totals && Object.keys(options.totals).length > 0) {
|
|
116
|
+
const r = ws.getRow(cursorRow);
|
|
117
|
+
const totalsLabel = options.totalsLabel ?? 'Total';
|
|
118
|
+
let labelWritten = false;
|
|
119
|
+
columns.forEach((c, i) => {
|
|
120
|
+
const cell = r.getCell(i + 1);
|
|
121
|
+
cell.font = { bold: true };
|
|
122
|
+
cell.fill = TOTAL_FILL;
|
|
123
|
+
cell.border = { ...THIN_BORDER, top: { style: 'medium', color: { argb: 'FF1F61FE' } } };
|
|
124
|
+
cell.alignment = { horizontal: columnAlign(c), vertical: 'middle' };
|
|
125
|
+
if (c.field in options.totals!) {
|
|
126
|
+
cell.value = cellValue(options.totals![c.field], c.displayFormat) as ExcelJS.CellValue;
|
|
127
|
+
if (c.displayFormat === 'currency') cell.numFmt = '#,##0.00';
|
|
128
|
+
} else if (!labelWritten) {
|
|
129
|
+
cell.value = totalsLabel;
|
|
130
|
+
labelWritten = true;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
cursorRow += 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
columns.forEach((c, i) => {
|
|
137
|
+
const headerLen = c.title.length;
|
|
138
|
+
const sampleLen = data.slice(0, 50).reduce((max, row) => {
|
|
139
|
+
const v = cellValue(row[c.field], c.displayFormat);
|
|
140
|
+
return Math.max(max, String(v ?? '').length);
|
|
141
|
+
}, 0);
|
|
142
|
+
ws.getColumn(i + 1).width = Math.min(Math.max(headerLen, sampleLen) + 2, 40);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Freeze everything above and including the column header row.
|
|
146
|
+
ws.views = [{ state: 'frozen', ySplit: headerRowIndex }];
|
|
147
|
+
|
|
148
|
+
const buf = await wb.xlsx.writeBuffer();
|
|
149
|
+
const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
150
|
+
const url = URL.createObjectURL(blob);
|
|
151
|
+
const a = document.createElement('a');
|
|
152
|
+
a.href = url;
|
|
153
|
+
a.download = `${sheetName}.xlsx`;
|
|
154
|
+
document.body.appendChild(a);
|
|
155
|
+
a.click();
|
|
156
|
+
a.remove();
|
|
157
|
+
URL.revokeObjectURL(url);
|
|
158
|
+
}
|
|
27
159
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
XLSX.utils.book_append_sheet(wb, ws, sheetName.slice(0, 31));
|
|
31
|
-
XLSX.writeFile(wb, `${sheetName}.xlsx`);
|
|
160
|
+
function cellText(val: unknown, format?: string): string {
|
|
161
|
+
return String(cellValue(val, format));
|
|
32
162
|
}
|
|
33
163
|
|
|
34
164
|
export async function exportToPDF<T extends Record<string, unknown>>(
|
|
@@ -47,12 +177,7 @@ export async function exportToPDF<T extends Record<string, unknown>>(
|
|
|
47
177
|
|
|
48
178
|
autoTable(doc, {
|
|
49
179
|
head: [columns.map((c) => c.title)],
|
|
50
|
-
body: data.map((row) =>
|
|
51
|
-
columns.map((c) => {
|
|
52
|
-
const val = cellValue(row[c.field], c.displayFormat);
|
|
53
|
-
return String(val);
|
|
54
|
-
})
|
|
55
|
-
),
|
|
180
|
+
body: data.map((row) => columns.map((c) => cellText(row[c.field], c.displayFormat))),
|
|
56
181
|
startY: 22,
|
|
57
182
|
styles: { fontSize: 8 },
|
|
58
183
|
headStyles: { fillColor: [31, 97, 254] }, // Carbon blue
|
package/src/i18n.tsx
CHANGED
|
@@ -24,6 +24,7 @@ export type BosSharedLabels = {
|
|
|
24
24
|
toggleOn: string;
|
|
25
25
|
toggleOff: string;
|
|
26
26
|
emptyValue: string;
|
|
27
|
+
showInactive: string;
|
|
27
28
|
|
|
28
29
|
loading: string;
|
|
29
30
|
saving: string;
|
|
@@ -71,6 +72,7 @@ export function useBosSharedI18n(): BosSharedLabels {
|
|
|
71
72
|
toggleOn: t("bos7.toggleOn", "On"),
|
|
72
73
|
toggleOff: t("bos7.toggleOff", "Off"),
|
|
73
74
|
emptyValue: t("bos7.emptyValue", "—"),
|
|
75
|
+
showInactive: t("bos7.showInactive", "Tampilkan Nonaktif"),
|
|
74
76
|
|
|
75
77
|
loading: t("bos7.loading", "Memuat data..."),
|
|
76
78
|
saving: t("bos7.saving", "Memproses..."),
|