@eztrak/shared 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +228 -9
- package/dist/{chunk-J6FBBM4R.mjs → chunk-BLCEEXZR.mjs} +217 -5
- package/dist/chunk-BLCEEXZR.mjs.map +1 -0
- package/dist/chunk-GED6IT3S.mjs +1 -0
- package/dist/chunk-GED6IT3S.mjs.map +1 -0
- package/dist/{chunk-GIBPOQGL.mjs → chunk-GVIHLI67.mjs} +81 -2
- package/dist/chunk-GVIHLI67.mjs.map +1 -0
- package/dist/components/index.d.mts +30 -1
- package/dist/components/index.d.ts +30 -1
- package/dist/components/index.js +229 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +4 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +207 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -21
- package/dist/utils/index.mjs +6 -7
- package/package.json +1 -1
- package/dist/chunk-5OG22DIX.mjs +0 -82
- package/dist/chunk-5OG22DIX.mjs.map +0 -1
- package/dist/chunk-GIBPOQGL.mjs.map +0 -1
- package/dist/chunk-J6FBBM4R.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -8,13 +8,38 @@ Shared utilities, hooks, and UI components for Eztrak applications.
|
|
|
8
8
|
npm install @eztrak/shared
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Peer dependencies
|
|
11
|
+
Peer dependencies:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install react react-dom react-router-dom react-hot-toast sweetalert2
|
|
14
|
+
npm install react react-dom react-router-dom react-hot-toast sweetalert2 react-icons framer-motion
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Your app must render `<Toaster />` from react-hot-toast (e.g. in your root layout).
|
|
17
|
+
Your app must render `<Toaster />` from `react-hot-toast` (e.g. in your root layout).
|
|
18
|
+
|
|
19
|
+
Import loader styles once if you use the `Loader` component:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import "@eztrak/shared/components/loader.css";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`CustomCellEditor` does **not** require `loader.css` — it uses an inline SVG spinner for the save button.
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
From the monorepo root:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run build:shared
|
|
33
|
+
npm run storybook
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
From `packages/shared`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run build
|
|
40
|
+
npm run storybook
|
|
41
|
+
npm run build:storybook
|
|
42
|
+
```
|
|
18
43
|
|
|
19
44
|
## Usage
|
|
20
45
|
|
|
@@ -171,6 +196,77 @@ import {
|
|
|
171
196
|
|
|
172
197
|
`ResetColumnsButton` shows a SweetAlert2 confirmation before calling `onReset`.
|
|
173
198
|
|
|
199
|
+
### Components — CustomCellEditor
|
|
200
|
+
|
|
201
|
+
AG Grid cell editor with inline save/cancel buttons, keyboard shortcuts (Enter to save, Escape to cancel), and toast feedback. Implements the AG Grid `ICellEditor` ref API (`getValue`, `isCancelBeforeStart`, `isCancelAfterEnd`).
|
|
202
|
+
|
|
203
|
+
Your app owns the API call via `onSave` — the package does not use RTK Query or app-specific endpoints.
|
|
204
|
+
|
|
205
|
+
For narrow columns, set `cellEditorPopup: true` on the column definition so the editor (and save/cancel buttons) is not clipped.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { CustomCellEditor } from "@eztrak/shared/components";
|
|
209
|
+
import { ELITE_API } from "../constants/apiConstants";
|
|
210
|
+
import { createCellEditorOnSave } from "../utils/cellEditorOnSave";
|
|
211
|
+
|
|
212
|
+
// In column defs:
|
|
213
|
+
{
|
|
214
|
+
field: "name",
|
|
215
|
+
editable: true,
|
|
216
|
+
cellEditor: CustomCellEditor,
|
|
217
|
+
cellEditorPopup: true,
|
|
218
|
+
cellEditorSelector: (params) => ({
|
|
219
|
+
component: CustomCellEditor,
|
|
220
|
+
params: {
|
|
221
|
+
value: params.value,
|
|
222
|
+
name: "name",
|
|
223
|
+
entityId: params.data.id,
|
|
224
|
+
entityName: "Package",
|
|
225
|
+
inputType: "text",
|
|
226
|
+
onSave: createCellEditorOnSave([ELITE_API.PACKAGE]),
|
|
227
|
+
dropdownOptions: [], // when inputType is "dropdown"
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Example `createCellEditorOnSave` helper (elite-pilot pattern):
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
import { ELITE_API } from "../constants/apiConstants";
|
|
237
|
+
import { store } from "../redux/store";
|
|
238
|
+
import { generalApi } from "../redux/api/generalApi";
|
|
239
|
+
|
|
240
|
+
export function createCellEditorOnSave(invalidate = []) {
|
|
241
|
+
return async (payload) => {
|
|
242
|
+
const result = await store.dispatch(
|
|
243
|
+
generalApi.endpoints.updateRecord.initiate({
|
|
244
|
+
endpoint: ELITE_API.SAVE_CELL_EDIT,
|
|
245
|
+
data: payload,
|
|
246
|
+
invalidate: invalidate.map((endpoint) => ({
|
|
247
|
+
type: "General",
|
|
248
|
+
id: endpoint,
|
|
249
|
+
})),
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
if (result.error) {
|
|
253
|
+
throw new Error(result.error?.data?.message || "Failed to update record");
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Input types
|
|
260
|
+
|
|
261
|
+
| `inputType` | Behavior |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| `text` | Standard text input |
|
|
264
|
+
| `number` | Numeric input (filters non-numeric characters); `propertyValue` is sent as a `number` |
|
|
265
|
+
| `date` | Date picker (`YYYY-MM-DD` via `toDateInputValue`) |
|
|
266
|
+
| `dropdown` | Select from `dropdownOptions` |
|
|
267
|
+
|
|
268
|
+
Save is disabled when the value is unchanged (compares as strings so `42` and `"42"` are treated as equal). Pressing Enter while a save is in progress does not submit twice.
|
|
269
|
+
|
|
174
270
|
### Components — ResetFiltersButton
|
|
175
271
|
|
|
176
272
|
Clears all URL search params (`react-router-dom`) in one click. Self-contained — no app `Button` needed.
|
|
@@ -196,6 +292,104 @@ import { ResetFiltersButton } from "@eztrak/shared/components";
|
|
|
196
292
|
| `onReset` | `() => void` | — | Called after search params are cleared |
|
|
197
293
|
| `disabled` | `boolean` | `false` | Disables the button |
|
|
198
294
|
|
|
295
|
+
### Components — SearchInput
|
|
296
|
+
|
|
297
|
+
URL-synced search field with optional debounced live search. Requires a React Router context (`useSearchParams`).
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { SearchInput } from "@eztrak/shared/components";
|
|
301
|
+
|
|
302
|
+
// Submit on Enter or search icon click (updates ?query= in the URL)
|
|
303
|
+
<SearchInput placeholder="Search items..." />
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### Live search (debounced)
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
<SearchInput
|
|
310
|
+
placeholder="Search..."
|
|
311
|
+
liveSearch
|
|
312
|
+
debounceDelay={500}
|
|
313
|
+
/>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Custom URL param name
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
<SearchInput defaultParam="search" placeholder="Find a user..." />
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Custom styling and icon
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
<SearchInput
|
|
326
|
+
searchWrapperClass="border border-gray-200 shadow-sm"
|
|
327
|
+
searchClassName="w-full"
|
|
328
|
+
searchIconClassName="text-blue-500"
|
|
329
|
+
showIcon
|
|
330
|
+
/>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Hide icon / custom icon
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import { HiOutlineSearch } from "react-icons/hi";
|
|
337
|
+
|
|
338
|
+
<SearchInput showIcon={false} />
|
|
339
|
+
|
|
340
|
+
<SearchInput customIcon={<HiOutlineSearch className="h-5 w-5" />} />
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`SearchInput` reads and writes a single query param (default `query`). Clearing the input removes that param from the URL. The input stays in sync when the URL changes externally (e.g. browser back/forward or `ResetFiltersButton`).
|
|
344
|
+
|
|
345
|
+
| Prop | Type | Default | Description |
|
|
346
|
+
| --- | --- | --- | --- |
|
|
347
|
+
| `placeholder` | `string` | `"Search..."` | Input placeholder |
|
|
348
|
+
| `defaultParam` | `string` | `"query"` | URL search param key |
|
|
349
|
+
| `defaultValue` | `string` | — | Initial value when the param is absent |
|
|
350
|
+
| `liveSearch` | `boolean` | `false` | Update URL on input change (debounced) |
|
|
351
|
+
| `debounceDelay` | `number` | `500` | Debounce delay in ms when `liveSearch` is true |
|
|
352
|
+
| `showIcon` | `boolean` | `true` | Show the submit/search icon button |
|
|
353
|
+
| `customIcon` | `ReactNode \| null` | `null` | Replace the default search icon |
|
|
354
|
+
| `name` | `string` | `"search"` | Form input `name` (used on submit) |
|
|
355
|
+
| `type` | `string` | `"search"` | Input `type` attribute |
|
|
356
|
+
| `searchWrapperClass` | `string` | — | Classes for the form wrapper |
|
|
357
|
+
| `searchClassName` | `string` | — | Classes for the input |
|
|
358
|
+
| `searchBtn` | `string` | — | Classes for the icon button |
|
|
359
|
+
| `searchIconClassName` | `string` | — | Classes for the default search icon |
|
|
360
|
+
|
|
361
|
+
Also accepts standard `<input>` HTML attributes (except `name`, `type`, `value`, `onChange`, and `defaultValue`, which are managed internally).
|
|
362
|
+
|
|
363
|
+
### Components — Loader
|
|
364
|
+
|
|
365
|
+
Spinner for full-page or inline loading states. Requires `loader.css`:
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
import { Loader } from "@eztrak/shared/components";
|
|
369
|
+
import "@eztrak/shared/components/loader.css";
|
|
370
|
+
|
|
371
|
+
<Loader size="36px" color="#F54C00" />
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Prop | Type | Default | Description |
|
|
375
|
+
| --- | --- | --- | --- |
|
|
376
|
+
| `size` | `string` | `"50px"` | Spinner diameter |
|
|
377
|
+
| `color` | `string` | `"#F54C00"` | Spinner accent color |
|
|
378
|
+
| `speed` | `string` | `"1s"` | Animation duration |
|
|
379
|
+
| `className` | `string` | — | Extra class on the spinner element |
|
|
380
|
+
|
|
381
|
+
### Components — Modal
|
|
382
|
+
|
|
383
|
+
Animated modal dialog (uses `framer-motion`).
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
import { Modal } from "@eztrak/shared/components";
|
|
387
|
+
|
|
388
|
+
<Modal isOpen={open} onClose={() => setOpen(false)} title="Confirm">
|
|
389
|
+
<p>Modal content</p>
|
|
390
|
+
</Modal>
|
|
391
|
+
```
|
|
392
|
+
|
|
199
393
|
### Components — EztrakTabs
|
|
200
394
|
|
|
201
395
|
Import styles once in your app entry or layout:
|
|
@@ -315,6 +509,10 @@ import {
|
|
|
315
509
|
useGridHeight,
|
|
316
510
|
EztrakTabs,
|
|
317
511
|
CustomPagination,
|
|
512
|
+
SearchInput,
|
|
513
|
+
CustomCellEditor,
|
|
514
|
+
Loader,
|
|
515
|
+
Modal,
|
|
318
516
|
} from "@eztrak/shared";
|
|
319
517
|
```
|
|
320
518
|
|
|
@@ -323,12 +521,14 @@ import {
|
|
|
323
521
|
Run the component playground locally (dev-only, not published to npm):
|
|
324
522
|
|
|
325
523
|
```bash
|
|
524
|
+
# From monorepo root
|
|
525
|
+
npm run storybook
|
|
526
|
+
|
|
527
|
+
# From packages/shared
|
|
326
528
|
npm run storybook
|
|
327
|
-
# or from the monorepo root:
|
|
328
|
-
npm run storybook -w @eztrak/shared
|
|
329
529
|
```
|
|
330
530
|
|
|
331
|
-
Opens on [http://localhost:6006](http://localhost:6006) with stories for `EztrakTabs`, `CustomPagination`, and `
|
|
531
|
+
Opens on [http://localhost:6006](http://localhost:6006) with stories for `EztrakTabs`, `CustomPagination`, `ResetColumnsButton`, `CustomCellEditor`, and `Loader`.
|
|
332
532
|
|
|
333
533
|
## Exports
|
|
334
534
|
|
|
@@ -337,8 +537,9 @@ Opens on [http://localhost:6006](http://localhost:6006) with stories for `Eztrak
|
|
|
337
537
|
| `@eztrak/shared` | Main entry — re-exports utils, hooks, and components |
|
|
338
538
|
| `@eztrak/shared/utils` | `cn`, `handleApiError`, `confirmationAlert`, date/format helpers, API error helpers, form field helpers |
|
|
339
539
|
| `@eztrak/shared/hooks` | `usePaginationUrlSync`, `useGridHeight` |
|
|
340
|
-
| `@eztrak/shared/components` | `EztrakTabs`, `CustomPagination`, `
|
|
540
|
+
| `@eztrak/shared/components` | `EztrakTabs`, `CustomPagination`, `CustomCellEditor`, `SearchInput`, `ResetFiltersButton`, `Modal`, `Loader`, `TableLayoutToolbarControls`, `ResetColumnsButton`, and related types |
|
|
341
541
|
| `@eztrak/shared/components/tabs.css` | Default tab styles (CSS variables) |
|
|
542
|
+
| `@eztrak/shared/components/loader.css` | Loader spinner styles |
|
|
342
543
|
|
|
343
544
|
### EztrakTabs props
|
|
344
545
|
|
|
@@ -375,12 +576,30 @@ Keyboard: Arrow keys move between tabs; Home/End jump to first/last enabled tab.
|
|
|
375
576
|
|
|
376
577
|
`CustomPagination` writes `page` and `perPage` to the URL via `react-router-dom`. Your page should read those params, fetch data, and pass the result back as `paginationData`.
|
|
377
578
|
|
|
579
|
+
### CustomCellEditor props
|
|
580
|
+
|
|
581
|
+
| Prop | Type | Default | Description |
|
|
582
|
+
| --- | --- | --- | --- |
|
|
583
|
+
| `value` | `string \| number \| null` | required | Initial cell value (AG Grid passes via `cellEditorParams`) |
|
|
584
|
+
| `name` | `string` | required | Property name sent in save payload as `propertyName` |
|
|
585
|
+
| `stopEditing` | `() => void` | required | AG Grid callback to close the editor |
|
|
586
|
+
| `entityId` | `string \| number` | required | Entity id sent in save payload |
|
|
587
|
+
| `entityName` | `string` | `"Record"` | Entity type name sent in save payload |
|
|
588
|
+
| `inputType` | `CellEditorInputType` | `"text"` | `text`, `number`, `date`, or `dropdown` |
|
|
589
|
+
| `onSave` | `(payload: CellEditPayload) => Promise<void>` | required | Async save handler; throw on failure to show error toast |
|
|
590
|
+
| `dropdownOptions` | `DropdownOption[]` | `[]` | Options when `inputType` is `dropdown` |
|
|
591
|
+
|
|
592
|
+
`CellEditPayload` shape: `{ propertyName, propertyValue, entityId, entityName }`. For `inputType: "number"`, `propertyValue` is a `number` (via `parseFloat`).
|
|
593
|
+
|
|
378
594
|
## Requirements
|
|
379
595
|
|
|
380
596
|
- Node.js 18+
|
|
381
597
|
- React 18+ (for hooks and components)
|
|
382
|
-
- `react-router-dom`
|
|
383
|
-
- `
|
|
598
|
+
- `react-router-dom` — `CustomPagination`, `SearchInput`, `ResetFiltersButton`, and `usePaginationUrlSync` (optional peer; install if you use these)
|
|
599
|
+
- `react-hot-toast` — toast feedback in `CustomCellEditor` and `handleApiError`
|
|
600
|
+
- `sweetalert2` — `ResetColumnsButton` and `confirmationAlert`
|
|
601
|
+
- `react-icons` — `CustomCellEditor`, `SearchInput`, and `Modal`
|
|
602
|
+
- `framer-motion` — `Modal`
|
|
384
603
|
- Works with any bundler that supports the [Node.js `exports` field](https://nodejs.org/api/packages.html#exports)
|
|
385
604
|
|
|
386
605
|
## License
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cn,
|
|
3
|
-
confirmationAlert
|
|
4
|
-
|
|
3
|
+
confirmationAlert,
|
|
4
|
+
toDateInputValue
|
|
5
|
+
} from "./chunk-GVIHLI67.mjs";
|
|
5
6
|
|
|
6
7
|
// src/components/tabs/EztrakTabs.tsx
|
|
7
8
|
import {
|
|
@@ -682,7 +683,7 @@ function SearchInput({
|
|
|
682
683
|
{
|
|
683
684
|
onSubmit: handleSearch,
|
|
684
685
|
className: cn(
|
|
685
|
-
"flex items-center justify-between bg-white rounded-
|
|
686
|
+
"flex items-center justify-between bg-white rounded-xl px-2 h-11 border",
|
|
686
687
|
searchWrapperClass
|
|
687
688
|
),
|
|
688
689
|
children: [
|
|
@@ -919,6 +920,216 @@ function Modal({
|
|
|
919
920
|
return createPortal(modalContent, modalRoot);
|
|
920
921
|
}
|
|
921
922
|
|
|
923
|
+
// src/components/cell-editor/CustomCellEditor.tsx
|
|
924
|
+
import {
|
|
925
|
+
forwardRef,
|
|
926
|
+
useEffect as useEffect4,
|
|
927
|
+
useImperativeHandle,
|
|
928
|
+
useRef as useRef4,
|
|
929
|
+
useState as useState3
|
|
930
|
+
} from "react";
|
|
931
|
+
import toast from "react-hot-toast";
|
|
932
|
+
import { FaCheck, FaTimes } from "react-icons/fa";
|
|
933
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
934
|
+
function ButtonSpinner() {
|
|
935
|
+
return /* @__PURE__ */ jsxs6(
|
|
936
|
+
"svg",
|
|
937
|
+
{
|
|
938
|
+
className: "animate-spin",
|
|
939
|
+
width: 8,
|
|
940
|
+
height: 8,
|
|
941
|
+
viewBox: "0 0 24 24",
|
|
942
|
+
fill: "none",
|
|
943
|
+
"aria-hidden": true,
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ jsx7(
|
|
946
|
+
"circle",
|
|
947
|
+
{
|
|
948
|
+
cx: "12",
|
|
949
|
+
cy: "12",
|
|
950
|
+
r: "10",
|
|
951
|
+
stroke: "white",
|
|
952
|
+
strokeWidth: "4",
|
|
953
|
+
strokeOpacity: "0.25"
|
|
954
|
+
}
|
|
955
|
+
),
|
|
956
|
+
/* @__PURE__ */ jsx7(
|
|
957
|
+
"path",
|
|
958
|
+
{
|
|
959
|
+
d: "M12 2a10 10 0 0 1 10 10",
|
|
960
|
+
stroke: "white",
|
|
961
|
+
strokeWidth: "4",
|
|
962
|
+
strokeLinecap: "round"
|
|
963
|
+
}
|
|
964
|
+
)
|
|
965
|
+
]
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
function normalizeInitialValue(initialValue, inputType) {
|
|
970
|
+
if (inputType === "date" && initialValue != null && initialValue !== "") {
|
|
971
|
+
return toDateInputValue(String(initialValue));
|
|
972
|
+
}
|
|
973
|
+
if (initialValue == null) {
|
|
974
|
+
return "";
|
|
975
|
+
}
|
|
976
|
+
return initialValue;
|
|
977
|
+
}
|
|
978
|
+
var CustomCellEditor = forwardRef(
|
|
979
|
+
({
|
|
980
|
+
value: initialValue,
|
|
981
|
+
name,
|
|
982
|
+
inputType = "text",
|
|
983
|
+
stopEditing,
|
|
984
|
+
entityId,
|
|
985
|
+
entityName = "Record",
|
|
986
|
+
onSave,
|
|
987
|
+
dropdownOptions = []
|
|
988
|
+
}, ref) => {
|
|
989
|
+
const normalizedInitial = normalizeInitialValue(initialValue, inputType);
|
|
990
|
+
const [value, setValue] = useState3(normalizedInitial);
|
|
991
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
992
|
+
const inputRef = useRef4(null);
|
|
993
|
+
const selectRef = useRef4(null);
|
|
994
|
+
useImperativeHandle(ref, () => ({
|
|
995
|
+
getValue: () => value,
|
|
996
|
+
isCancelBeforeStart: () => false,
|
|
997
|
+
isCancelAfterEnd: () => false
|
|
998
|
+
}));
|
|
999
|
+
useEffect4(() => {
|
|
1000
|
+
if (inputType === "dropdown") {
|
|
1001
|
+
const el2 = selectRef.current;
|
|
1002
|
+
if (el2) {
|
|
1003
|
+
el2.focus();
|
|
1004
|
+
}
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const el = inputRef.current;
|
|
1008
|
+
if (!el) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
el.focus();
|
|
1012
|
+
if (inputType !== "date") {
|
|
1013
|
+
el.select();
|
|
1014
|
+
}
|
|
1015
|
+
}, [inputType]);
|
|
1016
|
+
const handleSave = async () => {
|
|
1017
|
+
if (isLoading) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (String(value) === String(normalizedInitial)) {
|
|
1021
|
+
stopEditing();
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (!entityId) {
|
|
1025
|
+
toast.error("Entity ID is required");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (!name) {
|
|
1029
|
+
toast.error("Property name is required");
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
setIsLoading(true);
|
|
1033
|
+
const payloadValue = inputType === "number" && value !== "" ? parseFloat(String(value)) : value;
|
|
1034
|
+
const payload = {
|
|
1035
|
+
propertyName: name,
|
|
1036
|
+
propertyValue: payloadValue,
|
|
1037
|
+
entityId,
|
|
1038
|
+
entityName
|
|
1039
|
+
};
|
|
1040
|
+
try {
|
|
1041
|
+
await onSave(payload);
|
|
1042
|
+
toast.success("Record updated successfully");
|
|
1043
|
+
stopEditing();
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
console.error("Update error:", error);
|
|
1046
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to update record";
|
|
1047
|
+
toast.error(errorMessage);
|
|
1048
|
+
} finally {
|
|
1049
|
+
setIsLoading(false);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
const handleCancel = () => {
|
|
1053
|
+
setValue(normalizedInitial);
|
|
1054
|
+
stopEditing();
|
|
1055
|
+
};
|
|
1056
|
+
const handleKeyDown = (event) => {
|
|
1057
|
+
if (event.key === "Enter") {
|
|
1058
|
+
event.preventDefault();
|
|
1059
|
+
handleSave();
|
|
1060
|
+
} else if (event.key === "Escape") {
|
|
1061
|
+
event.preventDefault();
|
|
1062
|
+
handleCancel();
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
const handleInputChange = (event) => {
|
|
1066
|
+
let newValue = event.target.value;
|
|
1067
|
+
if (inputType === "number") {
|
|
1068
|
+
newValue = newValue.replace(/[^0-9.-]/g, "");
|
|
1069
|
+
}
|
|
1070
|
+
setValue(newValue);
|
|
1071
|
+
};
|
|
1072
|
+
const handleDropdownChange = (event) => {
|
|
1073
|
+
setValue(event.target.value);
|
|
1074
|
+
};
|
|
1075
|
+
const isSaveDisabled = isLoading || String(value) === String(normalizedInitial);
|
|
1076
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex items-center w-full h-full min-h-0 overflow-hidden gap-0.5 min-w-[80px]", children: [
|
|
1077
|
+
inputType === "dropdown" ? /* @__PURE__ */ jsxs6(
|
|
1078
|
+
"select",
|
|
1079
|
+
{
|
|
1080
|
+
ref: selectRef,
|
|
1081
|
+
value: String(value),
|
|
1082
|
+
onChange: handleDropdownChange,
|
|
1083
|
+
onKeyDown: handleKeyDown,
|
|
1084
|
+
className: "flex-1 h-full p-0 text-[10px] border border-gray-300 rounded-sm focus:outline-none focus:ring-1 focus:ring-blue-400 min-w-0 truncate",
|
|
1085
|
+
disabled: isLoading,
|
|
1086
|
+
children: [
|
|
1087
|
+
/* @__PURE__ */ jsx7("option", { value: "", disabled: true, children: "Select option..." }),
|
|
1088
|
+
dropdownOptions.map((opt) => /* @__PURE__ */ jsx7("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1089
|
+
]
|
|
1090
|
+
}
|
|
1091
|
+
) : /* @__PURE__ */ jsx7(
|
|
1092
|
+
"input",
|
|
1093
|
+
{
|
|
1094
|
+
ref: inputRef,
|
|
1095
|
+
type: inputType,
|
|
1096
|
+
value: String(value),
|
|
1097
|
+
onChange: handleInputChange,
|
|
1098
|
+
onKeyDown: handleKeyDown,
|
|
1099
|
+
className: "flex-1 h-full p-0 text-[10px] border border-gray-300 rounded-sm focus:outline-none focus:ring-1 focus:ring-blue-400 min-w-0 truncate",
|
|
1100
|
+
placeholder: "...",
|
|
1101
|
+
disabled: isLoading
|
|
1102
|
+
}
|
|
1103
|
+
),
|
|
1104
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center shrink-0 gap-0.5", children: [
|
|
1105
|
+
/* @__PURE__ */ jsx7(
|
|
1106
|
+
"button",
|
|
1107
|
+
{
|
|
1108
|
+
type: "button",
|
|
1109
|
+
onClick: handleSave,
|
|
1110
|
+
disabled: isSaveDisabled,
|
|
1111
|
+
className: "flex items-center justify-center w-4 h-4 text-white bg-green-500 rounded-sm hover:bg-green-600 disabled:opacity-50",
|
|
1112
|
+
title: "Save (Enter)",
|
|
1113
|
+
children: isLoading ? /* @__PURE__ */ jsx7(ButtonSpinner, {}) : /* @__PURE__ */ jsx7(FaCheck, { size: 7, "aria-hidden": true })
|
|
1114
|
+
}
|
|
1115
|
+
),
|
|
1116
|
+
/* @__PURE__ */ jsx7(
|
|
1117
|
+
"button",
|
|
1118
|
+
{
|
|
1119
|
+
type: "button",
|
|
1120
|
+
onClick: handleCancel,
|
|
1121
|
+
disabled: isLoading,
|
|
1122
|
+
className: "flex items-center justify-center w-4 h-4 text-white bg-red-500 rounded-sm hover:bg-red-600 disabled:opacity-50",
|
|
1123
|
+
title: "Cancel (Escape)",
|
|
1124
|
+
children: /* @__PURE__ */ jsx7(FaTimes, { size: 7, "aria-hidden": true })
|
|
1125
|
+
}
|
|
1126
|
+
)
|
|
1127
|
+
] })
|
|
1128
|
+
] });
|
|
1129
|
+
}
|
|
1130
|
+
);
|
|
1131
|
+
CustomCellEditor.displayName = "CustomCellEditor";
|
|
1132
|
+
|
|
922
1133
|
export {
|
|
923
1134
|
EztrakTabs,
|
|
924
1135
|
ResetColumnsButton,
|
|
@@ -927,6 +1138,7 @@ export {
|
|
|
927
1138
|
ResetFiltersButton,
|
|
928
1139
|
SearchInput,
|
|
929
1140
|
Loader,
|
|
930
|
-
Modal
|
|
1141
|
+
Modal,
|
|
1142
|
+
CustomCellEditor
|
|
931
1143
|
};
|
|
932
|
-
//# sourceMappingURL=chunk-
|
|
1144
|
+
//# sourceMappingURL=chunk-BLCEEXZR.mjs.map
|