@bexis2/bexis2-core-ui 0.4.3 → 0.4.5
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 +11 -0
- package/dist/components/Table/Table.svelte +10 -2
- package/dist/components/Table/TableContent.svelte +115 -95
- package/dist/components/Table/TableContent.svelte.d.ts +1 -0
- package/dist/components/Table/TableFilter.svelte.d.ts +2 -2
- package/dist/components/Table/TableFilterServer.svelte +41 -23
- package/dist/components/Table/TableFilterServer.svelte.d.ts +2 -1
- package/dist/components/Table/shared.d.ts +4 -5
- package/dist/components/Table/shared.js +48 -11
- package/dist/models/Models.d.ts +1 -0
- package/package.json +25 -26
- package/src/lib/components/Table/Table.svelte +10 -2
- package/src/lib/components/Table/TableContent.svelte +121 -98
- package/src/lib/components/Table/TableFilterServer.svelte +53 -33
- package/src/lib/components/Table/shared.ts +58 -14
- package/src/lib/models/Models.ts +2 -1
package/README.md
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
# bexis-core-ui
|
|
2
|
+
## 0.4.5
|
|
3
|
+
- table
|
|
4
|
+
- adds searching for missing values in the filters #744
|
|
5
|
+
- missing values for every data type
|
|
6
|
+
- display patterns for date/time/datetime types
|
|
7
|
+
|
|
8
|
+
## 0.4.4
|
|
9
|
+
- update libs
|
|
10
|
+
- remove eslint-plugin-svelte3
|
|
11
|
+
- update svelte, sveltekit, typescript, tailwind ...
|
|
12
|
+
|
|
2
13
|
## 0.4.3
|
|
3
14
|
- table
|
|
4
15
|
- Enable searching on server-side
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
<script>import Table from "./TableContent.svelte";
|
|
2
2
|
export let config;
|
|
3
|
+
let fetched = false;
|
|
3
4
|
const data = config.data;
|
|
5
|
+
$:
|
|
6
|
+
if ($data.length > 0)
|
|
7
|
+
fetched = true;
|
|
4
8
|
</script>
|
|
5
9
|
|
|
6
|
-
{#key
|
|
7
|
-
<Table
|
|
10
|
+
{#key fetched}
|
|
11
|
+
<Table
|
|
12
|
+
{config}
|
|
13
|
+
on:fetch={(columns) => (config = { ...config, columns: columns.detail })}
|
|
14
|
+
on:action
|
|
15
|
+
/>
|
|
8
16
|
{/key}
|
|
@@ -66,7 +66,7 @@ let {
|
|
|
66
66
|
entityId = 0,
|
|
67
67
|
// Entity ID to send with the request
|
|
68
68
|
versionId = 0
|
|
69
|
-
// Version ID to send with the request
|
|
69
|
+
// Version ID to send with the request,
|
|
70
70
|
} = config;
|
|
71
71
|
let searchValue = "";
|
|
72
72
|
let isFetching = false;
|
|
@@ -162,7 +162,8 @@ const tableColumns = [
|
|
|
162
162
|
updateTable,
|
|
163
163
|
pageIndex,
|
|
164
164
|
toFilterableValueFn,
|
|
165
|
-
filters
|
|
165
|
+
filters,
|
|
166
|
+
toStringFn
|
|
166
167
|
}) : createRender(colFilterComponent ?? TableFilter, {
|
|
167
168
|
filterValue: filterValue2,
|
|
168
169
|
id,
|
|
@@ -270,9 +271,23 @@ const updateTable = async () => {
|
|
|
270
271
|
}
|
|
271
272
|
const response = await fetchData.json();
|
|
272
273
|
if (response.columns !== void 0) {
|
|
273
|
-
|
|
274
|
+
console.log(response);
|
|
275
|
+
columns = convertServerColumns(response.columns, columns);
|
|
276
|
+
const clientCols = response.columns.reduce((acc, col) => {
|
|
277
|
+
acc[col.key] = col.column;
|
|
278
|
+
return acc;
|
|
279
|
+
}, {});
|
|
280
|
+
const tmpArr = [];
|
|
281
|
+
response.data.forEach((row, index) => {
|
|
282
|
+
const tmp = {};
|
|
283
|
+
Object.keys(row).forEach((key) => {
|
|
284
|
+
tmp[clientCols[key]] = row[key];
|
|
285
|
+
});
|
|
286
|
+
tmpArr.push(tmp);
|
|
287
|
+
});
|
|
288
|
+
dispatch("fetch", columns);
|
|
289
|
+
$data = tmpArr;
|
|
274
290
|
}
|
|
275
|
-
$data = response.data;
|
|
276
291
|
$serverItems = response.count;
|
|
277
292
|
return response;
|
|
278
293
|
};
|
|
@@ -294,87 +309,90 @@ $:
|
|
|
294
309
|
</script>
|
|
295
310
|
|
|
296
311
|
<div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
$filterValue = searchValue;
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
<div class="relative w-full flex items-center">
|
|
308
|
-
<input
|
|
309
|
-
class="input p-2 border border-primary-500"
|
|
310
|
-
type="text"
|
|
311
|
-
bind:value={searchValue}
|
|
312
|
-
placeholder="Search rows..."
|
|
313
|
-
id="{tableId}-search"
|
|
314
|
-
/><button
|
|
315
|
-
type="reset"
|
|
316
|
-
class="absolute right-3 items-center"
|
|
317
|
-
on:click|preventDefault={() => {
|
|
318
|
-
searchValue = '';
|
|
319
|
-
sendModel.q = '';
|
|
320
|
-
$filterValue = '';
|
|
321
|
-
}}><Fa icon={faXmark} /></button
|
|
322
|
-
>
|
|
323
|
-
</div>
|
|
324
|
-
<button
|
|
325
|
-
type="submit"
|
|
326
|
-
class="btn variant-filled-primary"
|
|
327
|
-
on:click|preventDefault={() => {
|
|
328
|
-
$filterValue = searchValue;
|
|
312
|
+
{#if $data.length > 0 || (columns && Object.keys(columns).length > 0)}
|
|
313
|
+
<div class="table-container">
|
|
314
|
+
<!-- Enable the search filter if table is not empty -->
|
|
315
|
+
{#if !serverSide}
|
|
316
|
+
<form
|
|
317
|
+
class="flex gap-2"
|
|
318
|
+
on:submit|preventDefault={() => {
|
|
329
319
|
sendModel.q = searchValue;
|
|
330
|
-
|
|
320
|
+
$filterValue = searchValue;
|
|
321
|
+
}}
|
|
331
322
|
>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<!-- Enable the resetResize button if resizable !== 'none' -->
|
|
350
|
-
{#if resizable !== 'none'}
|
|
323
|
+
<div class="relative w-full flex items-center">
|
|
324
|
+
<input
|
|
325
|
+
class="input p-2 border border-primary-500"
|
|
326
|
+
type="text"
|
|
327
|
+
bind:value={searchValue}
|
|
328
|
+
placeholder="Search rows..."
|
|
329
|
+
id="{tableId}-search"
|
|
330
|
+
/><button
|
|
331
|
+
type="reset"
|
|
332
|
+
class="absolute right-3 items-center"
|
|
333
|
+
on:click|preventDefault={() => {
|
|
334
|
+
searchValue = '';
|
|
335
|
+
sendModel.q = '';
|
|
336
|
+
$filterValue = '';
|
|
337
|
+
}}><Fa icon={faXmark} /></button
|
|
338
|
+
>
|
|
339
|
+
</div>
|
|
351
340
|
<button
|
|
352
|
-
type="
|
|
353
|
-
class="btn
|
|
354
|
-
on:click|preventDefault={() =>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
{#if exportable}
|
|
359
|
-
<button
|
|
360
|
-
type="button"
|
|
361
|
-
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
362
|
-
on:click|preventDefault={() => exportAsCsv(tableId, $exportedData)}
|
|
363
|
-
>Export as CSV</button
|
|
341
|
+
type="submit"
|
|
342
|
+
class="btn variant-filled-primary"
|
|
343
|
+
on:click|preventDefault={() => {
|
|
344
|
+
$filterValue = searchValue;
|
|
345
|
+
sendModel.q = searchValue;
|
|
346
|
+
}}>Search</button
|
|
364
347
|
>
|
|
365
|
-
|
|
348
|
+
</form>
|
|
349
|
+
{/if}
|
|
350
|
+
|
|
351
|
+
<div class="flex justify-between items-center py-2 w-full">
|
|
352
|
+
<div>
|
|
353
|
+
<!-- Enable the fitToScreen toggle if toggle === true -->
|
|
354
|
+
{#if toggle}
|
|
355
|
+
<SlideToggle
|
|
356
|
+
name="slider-label"
|
|
357
|
+
active="bg-primary-500"
|
|
358
|
+
size="sm"
|
|
359
|
+
checked={fitToScreen}
|
|
360
|
+
id="{tableId}-toggle"
|
|
361
|
+
on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
|
|
362
|
+
>
|
|
363
|
+
{/if}
|
|
364
|
+
</div>
|
|
365
|
+
<div class="flex gap-2">
|
|
366
|
+
<!-- Enable the resetResize button if resizable !== 'none' -->
|
|
367
|
+
{#if resizable !== 'none'}
|
|
368
|
+
<button
|
|
369
|
+
type="button"
|
|
370
|
+
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
371
|
+
on:click|preventDefault={() =>
|
|
372
|
+
resetResize($headerRows, $pageRows, tableId, columns, resizable)}
|
|
373
|
+
>Reset sizing</button
|
|
374
|
+
>
|
|
375
|
+
{/if}
|
|
376
|
+
{#if exportable}
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
380
|
+
on:click|preventDefault={() => exportAsCsv(tableId, $exportedData)}
|
|
381
|
+
>Export as CSV</button
|
|
382
|
+
>
|
|
383
|
+
{/if}
|
|
384
|
+
</div>
|
|
366
385
|
</div>
|
|
367
|
-
</div>
|
|
368
386
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
387
|
+
<div class="overflow-auto" style="height: {height}px">
|
|
388
|
+
<table
|
|
389
|
+
{...$tableAttrs}
|
|
390
|
+
class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip"
|
|
391
|
+
id="{tableId}-table"
|
|
392
|
+
>
|
|
393
|
+
<!-- If table height is provided, making the top row sticky -->
|
|
394
|
+
<thead class={height != null && $pageRows.length > 0 ? `sticky top-0` : ''}>
|
|
395
|
+
<!-- {#if $data.length > 0} -->
|
|
378
396
|
{#each $headerRows as headerRow (headerRow.id)}
|
|
379
397
|
<Subscribe
|
|
380
398
|
rowAttrs={headerRow.attrs()}
|
|
@@ -382,7 +400,7 @@ $:
|
|
|
382
400
|
rowProps={headerRow.props()}
|
|
383
401
|
let:rowProps
|
|
384
402
|
>
|
|
385
|
-
<tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-
|
|
403
|
+
<tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-800">
|
|
386
404
|
{#each headerRow.cells as cell (cell.id)}
|
|
387
405
|
<Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
|
|
388
406
|
<th scope="col" class="!p-2" {...attrs} style={cellStyle(cell.id, columns)}>
|
|
@@ -428,16 +446,9 @@ $:
|
|
|
428
446
|
</tr>
|
|
429
447
|
</Subscribe>
|
|
430
448
|
{/each}
|
|
431
|
-
|
|
432
|
-
<div class="p-10"><Spinner /></div>
|
|
433
|
-
{:else}
|
|
434
|
-
<!-- Table is empty -->
|
|
435
|
-
<p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>
|
|
436
|
-
{/if}
|
|
437
|
-
</thead>
|
|
449
|
+
</thead>
|
|
438
450
|
|
|
439
|
-
|
|
440
|
-
{#if $data.length > 0}
|
|
451
|
+
<tbody class="overflow-auto" {...$tableBodyAttrs}>
|
|
441
452
|
{#each $pageRows as row (row.id)}
|
|
442
453
|
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
|
443
454
|
<tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
|
|
@@ -465,13 +476,22 @@ $:
|
|
|
465
476
|
</tr>
|
|
466
477
|
</Subscribe>
|
|
467
478
|
{/each}
|
|
468
|
-
|
|
469
|
-
</
|
|
470
|
-
</
|
|
479
|
+
</tbody>
|
|
480
|
+
</table>
|
|
481
|
+
</div>
|
|
471
482
|
</div>
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
483
|
+
{:else}
|
|
484
|
+
<div class="p-10 w-full h-full flex justify-center items-center bg-neutral-200 rounded">
|
|
485
|
+
<p>No data available</p>
|
|
486
|
+
</div>
|
|
487
|
+
{/if}
|
|
488
|
+
|
|
489
|
+
{#if isFetching}
|
|
490
|
+
<div class="p-10 w-full h-full flex justify-center items-center"><Spinner /></div>
|
|
491
|
+
{/if}
|
|
492
|
+
|
|
493
|
+
<!-- Adding pagination, if table is not empty -->
|
|
494
|
+
{#if $data.length > 0 || (columns && Object.keys(columns).length > 0)}
|
|
475
495
|
{#if serverSide}
|
|
476
496
|
<TablePaginationServer
|
|
477
497
|
{pageIndex}
|
|
@@ -4,8 +4,8 @@ declare const __propDef: {
|
|
|
4
4
|
values: any;
|
|
5
5
|
id: any;
|
|
6
6
|
tableId: any;
|
|
7
|
-
toFilterableValueFn?: ((value: any) => any)
|
|
8
|
-
toStringFn?: ((value: any) => string)
|
|
7
|
+
toFilterableValueFn?: undefined | ((value: any) => any);
|
|
8
|
+
toStringFn?: undefined | ((value: any) => string);
|
|
9
9
|
filterValue: any;
|
|
10
10
|
filters: any;
|
|
11
11
|
pageIndex: any;
|
|
@@ -9,7 +9,21 @@ export let toFilterableValueFn = void 0;
|
|
|
9
9
|
export let filters;
|
|
10
10
|
export let updateTable;
|
|
11
11
|
export let pageIndex;
|
|
12
|
+
export let toStringFn = void 0;
|
|
12
13
|
let active = false;
|
|
14
|
+
let type = "string";
|
|
15
|
+
let isDate = false;
|
|
16
|
+
let dropdowns = [];
|
|
17
|
+
$values.forEach((item) => {
|
|
18
|
+
if (item) {
|
|
19
|
+
type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
|
|
20
|
+
if (type === "object") {
|
|
21
|
+
if ((toFilterableValueFn ? toFilterableValueFn(item) : item) instanceof Date) {
|
|
22
|
+
isDate = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
13
27
|
const options = {
|
|
14
28
|
number: [
|
|
15
29
|
{
|
|
@@ -83,35 +97,34 @@ const options = {
|
|
|
83
97
|
{
|
|
84
98
|
value: FilterOptionsEnum.b,
|
|
85
99
|
label: "Is before"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
value: FilterOptionsEnum.no,
|
|
89
|
-
label: "Is not on"
|
|
90
100
|
}
|
|
101
|
+
// TODO: 'Not on' filter should be fixed on the server side
|
|
102
|
+
// {
|
|
103
|
+
// value: FilterOptionsEnum.no,
|
|
104
|
+
// label: 'Is not on'
|
|
105
|
+
// }
|
|
91
106
|
]
|
|
92
107
|
};
|
|
93
|
-
let dropdowns = [];
|
|
94
108
|
const popupId = `${tableId}-${id}`;
|
|
95
109
|
const popupFeatured = {
|
|
96
110
|
event: "click",
|
|
97
111
|
target: popupId,
|
|
98
112
|
placement: "bottom-start"
|
|
99
113
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
});
|
|
114
|
+
const stringValues = $values.map((item) => toStringFn ? toStringFn(item) : item);
|
|
115
|
+
const missingValues = stringValues.reduce((acc, item, index) => {
|
|
116
|
+
acc[typeof item === "string" ? item.toLowerCase() : item] = $values[index];
|
|
117
|
+
return acc;
|
|
118
|
+
}, {});
|
|
119
|
+
const getMissingValue = (value) => {
|
|
120
|
+
return Object.keys(missingValues).includes(value.toLowerCase()) ? missingValues[value.toLowerCase()] : value;
|
|
121
|
+
};
|
|
112
122
|
const optionChangeHandler = (e, index) => {
|
|
113
123
|
delete $filters[id][dropdowns[index].option];
|
|
114
|
-
$filters[id] = {
|
|
124
|
+
$filters[id] = {
|
|
125
|
+
...$filters[id],
|
|
126
|
+
[e.target.value]: getMissingValue(dropdowns[index].value)
|
|
127
|
+
};
|
|
115
128
|
$filters = $filters;
|
|
116
129
|
dropdowns[index] = {
|
|
117
130
|
...dropdowns[index],
|
|
@@ -121,11 +134,14 @@ const optionChangeHandler = (e, index) => {
|
|
|
121
134
|
const valueChangeHandler = (e, index) => {
|
|
122
135
|
dropdowns[index] = {
|
|
123
136
|
...dropdowns[index],
|
|
124
|
-
value: type === "
|
|
137
|
+
value: type === "date" ? new Date(e.target.value) : e.target.value
|
|
125
138
|
};
|
|
126
139
|
$filters = {
|
|
127
140
|
...$filters,
|
|
128
|
-
[id]: {
|
|
141
|
+
[id]: {
|
|
142
|
+
...$filters[id],
|
|
143
|
+
[dropdowns[index].option]: type === "number" ? parseFloat(getMissingValue(e.target.value)) || void 0 : getMissingValue(e.target.value)
|
|
144
|
+
}
|
|
129
145
|
};
|
|
130
146
|
};
|
|
131
147
|
const addFilter = (option, value) => {
|
|
@@ -165,6 +181,8 @@ $:
|
|
|
165
181
|
);
|
|
166
182
|
$:
|
|
167
183
|
addFilter(options[type][0].value, void 0);
|
|
184
|
+
$:
|
|
185
|
+
console.log($filters);
|
|
168
186
|
</script>
|
|
169
187
|
|
|
170
188
|
<form class="">
|
|
@@ -222,14 +240,14 @@ $:
|
|
|
222
240
|
{/if}
|
|
223
241
|
</div>
|
|
224
242
|
|
|
225
|
-
{#if type === 'number'}
|
|
243
|
+
<!-- {#if type === 'number'}
|
|
226
244
|
<input
|
|
227
245
|
type="number"
|
|
228
246
|
class="input p-1 border border-primary-500"
|
|
229
247
|
on:input={(e) => valueChangeHandler(e, index)}
|
|
230
248
|
bind:value={dropdown.value}
|
|
231
|
-
/>
|
|
232
|
-
{
|
|
249
|
+
/> -->
|
|
250
|
+
{#if type === 'number' || type === 'string'}
|
|
233
251
|
<input
|
|
234
252
|
type="text"
|
|
235
253
|
class="input p-1 border border-primary-500"
|
|
@@ -4,10 +4,11 @@ declare const __propDef: {
|
|
|
4
4
|
values: any;
|
|
5
5
|
id: any;
|
|
6
6
|
tableId: any;
|
|
7
|
-
toFilterableValueFn?: ((value: any) => any)
|
|
7
|
+
toFilterableValueFn?: undefined | ((value: any) => any);
|
|
8
8
|
filters: any;
|
|
9
9
|
updateTable: any;
|
|
10
10
|
pageIndex: any;
|
|
11
|
+
toStringFn?: undefined | ((value: any) => string);
|
|
11
12
|
};
|
|
12
13
|
events: {
|
|
13
14
|
[evt: string]: CustomEvent<any>;
|
|
@@ -25,8 +25,7 @@ export declare const normalizeFilters: (filters: {
|
|
|
25
25
|
}) => Filter[];
|
|
26
26
|
export declare const exportAsCsv: (tableId: string, exportedData: string) => void;
|
|
27
27
|
export declare const resetResize: (headerRows: any, pageRows: any, tableId: string, columns: Columns | undefined, resizable: 'none' | 'rows' | 'columns' | 'both') => void;
|
|
28
|
-
export declare const missingValuesFn: (key: number, missingValues: {
|
|
29
|
-
[key: string]: string;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export declare const convertServerColumns: (columns: ServerColumn[]) => Columns;
|
|
28
|
+
export declare const missingValuesFn: (key: number | string, missingValues: {
|
|
29
|
+
[key: string | number]: string;
|
|
30
|
+
}) => string | number;
|
|
31
|
+
export declare const convertServerColumns: (serverColumns: ServerColumn[], columns: Columns | undefined) => Columns;
|
|
@@ -89,29 +89,66 @@ export const resetResize = (headerRows, pageRows, tableId, columns, resizable) =
|
|
|
89
89
|
}
|
|
90
90
|
};
|
|
91
91
|
export const missingValuesFn = (key, missingValues) => {
|
|
92
|
-
|
|
92
|
+
const foundKey = typeof key === 'number' && key.toString().includes('e')
|
|
93
|
+
? Object.keys(missingValues).find((item) => {
|
|
94
|
+
return item.toLowerCase() === key.toString().toLowerCase();
|
|
95
|
+
})
|
|
96
|
+
: typeof key === 'string' && parseInt(key).toString().length !== key.length && new Date(key)
|
|
97
|
+
? Object.keys(missingValues).find((item) => new Date(item).getTime() === new Date(key).getTime())
|
|
98
|
+
: key in missingValues
|
|
99
|
+
? key
|
|
100
|
+
: undefined;
|
|
101
|
+
return foundKey ? missingValues[foundKey] : key;
|
|
93
102
|
};
|
|
94
|
-
export const convertServerColumns = (columns) => {
|
|
103
|
+
export const convertServerColumns = (serverColumns, columns) => {
|
|
95
104
|
const columnsConfig = {};
|
|
96
|
-
|
|
105
|
+
serverColumns.forEach((col) => {
|
|
97
106
|
let instructions = {};
|
|
98
107
|
if (col.instructions?.displayPattern) {
|
|
108
|
+
let dp = col.instructions.displayPattern;
|
|
109
|
+
// Swap 'm' and 'M' to match the backend date format
|
|
110
|
+
for (let i = 0; i < col.instructions.displayPattern.length; i++) {
|
|
111
|
+
if (col.instructions.displayPattern[i] === 'm') {
|
|
112
|
+
dp = `${dp.slice(0, i)}M${dp.slice(i + 1)}`;
|
|
113
|
+
}
|
|
114
|
+
else if (col.instructions.displayPattern[i] === 'M') {
|
|
115
|
+
dp = `${dp.slice(0, i)}m${dp.slice(i + 1)}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
99
118
|
instructions = {
|
|
100
|
-
toStringFn: (date) =>
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
toStringFn: (date) => {
|
|
120
|
+
if (col.instructions?.missingValues) {
|
|
121
|
+
const missingValue = missingValuesFn(date, col.instructions?.missingValues || {});
|
|
122
|
+
if (missingValue === date) {
|
|
123
|
+
return dateFormat(new Date(date), dp);
|
|
124
|
+
}
|
|
125
|
+
return missingValue;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return dateFormat(new Date(date), dp);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
toSortableValueFn: (date) => new Date(date).getTime(),
|
|
132
|
+
toFilterableValueFn: (date) => new Date(date)
|
|
103
133
|
};
|
|
104
134
|
}
|
|
105
|
-
if (col.instructions?.missingValues) {
|
|
135
|
+
else if (col.instructions?.missingValues) {
|
|
106
136
|
instructions = {
|
|
107
137
|
...instructions,
|
|
108
138
|
toStringFn: (key) => missingValuesFn(key, col.instructions?.missingValues || {})
|
|
109
139
|
};
|
|
110
140
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
if (columns && col.column in columns) {
|
|
142
|
+
columnsConfig[col.column] = {
|
|
143
|
+
...columns[col.column],
|
|
144
|
+
instructions
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
columnsConfig[col.column] = {
|
|
149
|
+
instructions
|
|
150
|
+
};
|
|
151
|
+
}
|
|
115
152
|
});
|
|
116
153
|
return columnsConfig;
|
|
117
154
|
};
|
package/dist/models/Models.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bexis2/bexis2-core-ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -20,33 +20,31 @@
|
|
|
20
20
|
"3.npm - publish": "npm publish --access public"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@playwright/test": "^1.
|
|
23
|
+
"@playwright/test": "^1.43.0",
|
|
24
24
|
"@skeletonlabs/skeleton": "^2.9.0",
|
|
25
25
|
"@skeletonlabs/tw-plugin": "^0.3.1",
|
|
26
|
-
"@sveltejs/adapter-auto": "^3.
|
|
26
|
+
"@sveltejs/adapter-auto": "^3.2.0",
|
|
27
27
|
"@sveltejs/adapter-static": "^3.0.1",
|
|
28
|
-
"@sveltejs/kit": "^2.5.3",
|
|
29
|
-
"@sveltejs/package": "^2.3.0",
|
|
30
28
|
"@tailwindcss/forms": "^0.5.7",
|
|
31
|
-
"@tailwindcss/typography": "^0.5.
|
|
32
|
-
"@types/node": "^20.
|
|
33
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
34
|
-
"@typescript-eslint/parser": "^7.
|
|
35
|
-
"autoprefixer": "^10.4.
|
|
36
|
-
"eslint": "^8.
|
|
37
|
-
"eslint-config-prettier": "^
|
|
38
|
-
"
|
|
39
|
-
"postcss": "^8.4.35",
|
|
29
|
+
"@tailwindcss/typography": "^0.5.12",
|
|
30
|
+
"@types/node": "^20.12.5",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
|
32
|
+
"@typescript-eslint/parser": "^7.5.0",
|
|
33
|
+
"autoprefixer": "^10.4.19",
|
|
34
|
+
"eslint": "^8.0.0",
|
|
35
|
+
"eslint-config-prettier": "^8.0.0",
|
|
36
|
+
"postcss": "^8.4.38",
|
|
40
37
|
"prettier": "^3.2.5",
|
|
41
38
|
"prettier-plugin-svelte": "^3.2.2",
|
|
42
39
|
"raw-loader": "^4.0.2",
|
|
43
40
|
"svelte": "^4.2.12",
|
|
44
|
-
"svelte-check": "^3.6.
|
|
45
|
-
"
|
|
41
|
+
"svelte-check": "^3.6.9",
|
|
42
|
+
"@sveltejs/package": "^2.3.1",
|
|
43
|
+
"tailwindcss": "^3.4.3",
|
|
46
44
|
"tslib": "^2.6.2",
|
|
47
|
-
"typescript": "^5.4.
|
|
48
|
-
"vite": "^5.
|
|
49
|
-
"vitest": "^1.
|
|
45
|
+
"typescript": "^5.4.4",
|
|
46
|
+
"vite": "^5.2.8",
|
|
47
|
+
"vitest": "^1.4.0"
|
|
50
48
|
},
|
|
51
49
|
"type": "module",
|
|
52
50
|
"module": "./src/lib/index.ts",
|
|
@@ -67,11 +65,11 @@
|
|
|
67
65
|
"@codemirror/lint": "^6.5.0",
|
|
68
66
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
69
67
|
"@floating-ui/dom": "^1.6.3",
|
|
70
|
-
"@fortawesome/fontawesome-free": "^6.5.
|
|
71
|
-
"@fortawesome/fontawesome-svg-core": "^6.5.
|
|
72
|
-
"@fortawesome/free-regular-svg-icons": "^6.5.
|
|
73
|
-
"@fortawesome/free-solid-svg-icons": "^6.5.
|
|
74
|
-
"axios": "^1.6.
|
|
68
|
+
"@fortawesome/fontawesome-free": "^6.5.2",
|
|
69
|
+
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
|
70
|
+
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
|
71
|
+
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
|
72
|
+
"axios": "^1.6.8",
|
|
75
73
|
"codemirror": "^6.0.1",
|
|
76
74
|
"dateformat": "^5.0.3",
|
|
77
75
|
"delay": "^6.0.0",
|
|
@@ -82,10 +80,11 @@
|
|
|
82
80
|
"svelte": "^4.2.12",
|
|
83
81
|
"svelte-codemirror-editor": "^1.3.0",
|
|
84
82
|
"svelte-fa": "^4.0.2",
|
|
85
|
-
"svelte-file-dropzone": "^2.0.
|
|
83
|
+
"svelte-file-dropzone": "^2.0.7",
|
|
86
84
|
"svelte-headless-table": "^0.18.2",
|
|
87
85
|
"svelte-select": "5.8.3",
|
|
88
|
-
"
|
|
86
|
+
"@sveltejs/kit": "^2.5.5",
|
|
87
|
+
"vest": "^5.2.12"
|
|
89
88
|
},
|
|
90
89
|
"author": "David Schöne",
|
|
91
90
|
"license": "ISC",
|
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
import type { TableConfig } from '$models/Models';
|
|
4
4
|
|
|
5
5
|
export let config: TableConfig<any>;
|
|
6
|
+
|
|
7
|
+
let fetched = false;
|
|
6
8
|
const data = config.data;
|
|
9
|
+
|
|
10
|
+
$: if ($data.length > 0) fetched = true;
|
|
7
11
|
</script>
|
|
8
12
|
|
|
9
|
-
{#key
|
|
10
|
-
<Table
|
|
13
|
+
{#key fetched}
|
|
14
|
+
<Table
|
|
15
|
+
{config}
|
|
16
|
+
on:fetch={(columns) => (config = { ...config, columns: columns.detail })}
|
|
17
|
+
on:action
|
|
18
|
+
/>
|
|
11
19
|
{/key}
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
resetResize,
|
|
34
34
|
convertServerColumns
|
|
35
35
|
} from './shared';
|
|
36
|
-
import { Receive, Send } from '$
|
|
37
|
-
import type { TableConfig } from '$
|
|
36
|
+
import { Receive, Send } from '$models/Models';
|
|
37
|
+
import type { TableConfig } from '$models/Models';
|
|
38
38
|
import type { FilterOptionsEnum } from '$models/Enums';
|
|
39
39
|
|
|
40
40
|
export let config: TableConfig<any>;
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
token = '', // Bearer token to authenticate the request
|
|
59
59
|
sendModel = new Send(), // Model to send requests
|
|
60
60
|
entityId = 0, // Entity ID to send with the request
|
|
61
|
-
versionId = 0 // Version ID to send with the request
|
|
61
|
+
versionId = 0 // Version ID to send with the request,
|
|
62
62
|
} = config;
|
|
63
63
|
|
|
64
64
|
let searchValue = '';
|
|
@@ -188,7 +188,8 @@
|
|
|
188
188
|
updateTable,
|
|
189
189
|
pageIndex,
|
|
190
190
|
toFilterableValueFn,
|
|
191
|
-
filters
|
|
191
|
+
filters,
|
|
192
|
+
toStringFn
|
|
192
193
|
})
|
|
193
194
|
: createRender(colFilterComponent ?? TableFilter, {
|
|
194
195
|
filterValue,
|
|
@@ -318,11 +319,28 @@
|
|
|
318
319
|
|
|
319
320
|
// Format server columns to the client columns
|
|
320
321
|
if (response.columns !== undefined) {
|
|
321
|
-
|
|
322
|
+
console.log(response);
|
|
323
|
+
|
|
324
|
+
columns = convertServerColumns(response.columns, columns);
|
|
325
|
+
|
|
326
|
+
const clientCols = response.columns.reduce((acc, col) => {
|
|
327
|
+
acc[col.key] = col.column;
|
|
328
|
+
return acc;
|
|
329
|
+
}, {});
|
|
330
|
+
|
|
331
|
+
const tmpArr: any[] = [];
|
|
332
|
+
|
|
333
|
+
response.data.forEach((row, index) => {
|
|
334
|
+
const tmp: { [key: string]: any } = {};
|
|
335
|
+
Object.keys(row).forEach((key) => {
|
|
336
|
+
tmp[clientCols[key]] = row[key];
|
|
337
|
+
});
|
|
338
|
+
tmpArr.push(tmp);
|
|
339
|
+
});
|
|
340
|
+
dispatch('fetch', columns);
|
|
341
|
+
$data = tmpArr;
|
|
322
342
|
}
|
|
323
343
|
|
|
324
|
-
// Update data store
|
|
325
|
-
$data = response.data;
|
|
326
344
|
$serverItems = response.count;
|
|
327
345
|
|
|
328
346
|
return response;
|
|
@@ -348,87 +366,90 @@
|
|
|
348
366
|
</script>
|
|
349
367
|
|
|
350
368
|
<div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
$filterValue = searchValue;
|
|
359
|
-
}}
|
|
360
|
-
>
|
|
361
|
-
<div class="relative w-full flex items-center">
|
|
362
|
-
<input
|
|
363
|
-
class="input p-2 border border-primary-500"
|
|
364
|
-
type="text"
|
|
365
|
-
bind:value={searchValue}
|
|
366
|
-
placeholder="Search rows..."
|
|
367
|
-
id="{tableId}-search"
|
|
368
|
-
/><button
|
|
369
|
-
type="reset"
|
|
370
|
-
class="absolute right-3 items-center"
|
|
371
|
-
on:click|preventDefault={() => {
|
|
372
|
-
searchValue = '';
|
|
373
|
-
sendModel.q = '';
|
|
374
|
-
$filterValue = '';
|
|
375
|
-
}}><Fa icon={faXmark} /></button
|
|
376
|
-
>
|
|
377
|
-
</div>
|
|
378
|
-
<button
|
|
379
|
-
type="submit"
|
|
380
|
-
class="btn variant-filled-primary"
|
|
381
|
-
on:click|preventDefault={() => {
|
|
382
|
-
$filterValue = searchValue;
|
|
369
|
+
{#if $data.length > 0 || (columns && Object.keys(columns).length > 0)}
|
|
370
|
+
<div class="table-container">
|
|
371
|
+
<!-- Enable the search filter if table is not empty -->
|
|
372
|
+
{#if !serverSide}
|
|
373
|
+
<form
|
|
374
|
+
class="flex gap-2"
|
|
375
|
+
on:submit|preventDefault={() => {
|
|
383
376
|
sendModel.q = searchValue;
|
|
384
|
-
|
|
377
|
+
$filterValue = searchValue;
|
|
378
|
+
}}
|
|
385
379
|
>
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<!-- Enable the resetResize button if resizable !== 'none' -->
|
|
404
|
-
{#if resizable !== 'none'}
|
|
405
|
-
<button
|
|
406
|
-
type="button"
|
|
407
|
-
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
408
|
-
on:click|preventDefault={() =>
|
|
409
|
-
resetResize($headerRows, $pageRows, tableId, columns, resizable)}>Reset sizing</button
|
|
410
|
-
>
|
|
411
|
-
{/if}
|
|
412
|
-
{#if exportable}
|
|
380
|
+
<div class="relative w-full flex items-center">
|
|
381
|
+
<input
|
|
382
|
+
class="input p-2 border border-primary-500"
|
|
383
|
+
type="text"
|
|
384
|
+
bind:value={searchValue}
|
|
385
|
+
placeholder="Search rows..."
|
|
386
|
+
id="{tableId}-search"
|
|
387
|
+
/><button
|
|
388
|
+
type="reset"
|
|
389
|
+
class="absolute right-3 items-center"
|
|
390
|
+
on:click|preventDefault={() => {
|
|
391
|
+
searchValue = '';
|
|
392
|
+
sendModel.q = '';
|
|
393
|
+
$filterValue = '';
|
|
394
|
+
}}><Fa icon={faXmark} /></button
|
|
395
|
+
>
|
|
396
|
+
</div>
|
|
413
397
|
<button
|
|
414
|
-
type="
|
|
415
|
-
class="btn
|
|
416
|
-
on:click|preventDefault={() =>
|
|
417
|
-
|
|
398
|
+
type="submit"
|
|
399
|
+
class="btn variant-filled-primary"
|
|
400
|
+
on:click|preventDefault={() => {
|
|
401
|
+
$filterValue = searchValue;
|
|
402
|
+
sendModel.q = searchValue;
|
|
403
|
+
}}>Search</button
|
|
418
404
|
>
|
|
419
|
-
|
|
405
|
+
</form>
|
|
406
|
+
{/if}
|
|
407
|
+
|
|
408
|
+
<div class="flex justify-between items-center py-2 w-full">
|
|
409
|
+
<div>
|
|
410
|
+
<!-- Enable the fitToScreen toggle if toggle === true -->
|
|
411
|
+
{#if toggle}
|
|
412
|
+
<SlideToggle
|
|
413
|
+
name="slider-label"
|
|
414
|
+
active="bg-primary-500"
|
|
415
|
+
size="sm"
|
|
416
|
+
checked={fitToScreen}
|
|
417
|
+
id="{tableId}-toggle"
|
|
418
|
+
on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
|
|
419
|
+
>
|
|
420
|
+
{/if}
|
|
421
|
+
</div>
|
|
422
|
+
<div class="flex gap-2">
|
|
423
|
+
<!-- Enable the resetResize button if resizable !== 'none' -->
|
|
424
|
+
{#if resizable !== 'none'}
|
|
425
|
+
<button
|
|
426
|
+
type="button"
|
|
427
|
+
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
428
|
+
on:click|preventDefault={() =>
|
|
429
|
+
resetResize($headerRows, $pageRows, tableId, columns, resizable)}
|
|
430
|
+
>Reset sizing</button
|
|
431
|
+
>
|
|
432
|
+
{/if}
|
|
433
|
+
{#if exportable}
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
class="btn btn-sm variant-filled-primary rounded-full order-last"
|
|
437
|
+
on:click|preventDefault={() => exportAsCsv(tableId, $exportedData)}
|
|
438
|
+
>Export as CSV</button
|
|
439
|
+
>
|
|
440
|
+
{/if}
|
|
441
|
+
</div>
|
|
420
442
|
</div>
|
|
421
|
-
</div>
|
|
422
443
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
444
|
+
<div class="overflow-auto" style="height: {height}px">
|
|
445
|
+
<table
|
|
446
|
+
{...$tableAttrs}
|
|
447
|
+
class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip"
|
|
448
|
+
id="{tableId}-table"
|
|
449
|
+
>
|
|
450
|
+
<!-- If table height is provided, making the top row sticky -->
|
|
451
|
+
<thead class={height != null && $pageRows.length > 0 ? `sticky top-0` : ''}>
|
|
452
|
+
<!-- {#if $data.length > 0} -->
|
|
432
453
|
{#each $headerRows as headerRow (headerRow.id)}
|
|
433
454
|
<Subscribe
|
|
434
455
|
rowAttrs={headerRow.attrs()}
|
|
@@ -436,7 +457,7 @@
|
|
|
436
457
|
rowProps={headerRow.props()}
|
|
437
458
|
let:rowProps
|
|
438
459
|
>
|
|
439
|
-
<tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-
|
|
460
|
+
<tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-800">
|
|
440
461
|
{#each headerRow.cells as cell (cell.id)}
|
|
441
462
|
<Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
|
|
442
463
|
<th scope="col" class="!p-2" {...attrs} style={cellStyle(cell.id, columns)}>
|
|
@@ -482,16 +503,9 @@
|
|
|
482
503
|
</tr>
|
|
483
504
|
</Subscribe>
|
|
484
505
|
{/each}
|
|
485
|
-
|
|
486
|
-
<div class="p-10"><Spinner /></div>
|
|
487
|
-
{:else}
|
|
488
|
-
<!-- Table is empty -->
|
|
489
|
-
<p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>
|
|
490
|
-
{/if}
|
|
491
|
-
</thead>
|
|
506
|
+
</thead>
|
|
492
507
|
|
|
493
|
-
|
|
494
|
-
{#if $data.length > 0}
|
|
508
|
+
<tbody class="overflow-auto" {...$tableBodyAttrs}>
|
|
495
509
|
{#each $pageRows as row (row.id)}
|
|
496
510
|
<Subscribe rowAttrs={row.attrs()} let:rowAttrs>
|
|
497
511
|
<tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
|
|
@@ -519,13 +533,22 @@
|
|
|
519
533
|
</tr>
|
|
520
534
|
</Subscribe>
|
|
521
535
|
{/each}
|
|
522
|
-
|
|
523
|
-
</
|
|
524
|
-
</
|
|
536
|
+
</tbody>
|
|
537
|
+
</table>
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
{:else}
|
|
541
|
+
<div class="p-10 w-full h-full flex justify-center items-center bg-neutral-200 rounded">
|
|
542
|
+
<p>No data available</p>
|
|
525
543
|
</div>
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
544
|
+
{/if}
|
|
545
|
+
|
|
546
|
+
{#if isFetching}
|
|
547
|
+
<div class="p-10 w-full h-full flex justify-center items-center"><Spinner /></div>
|
|
548
|
+
{/if}
|
|
549
|
+
|
|
550
|
+
<!-- Adding pagination, if table is not empty -->
|
|
551
|
+
{#if $data.length > 0 || (columns && Object.keys(columns).length > 0)}
|
|
529
552
|
{#if serverSide}
|
|
530
553
|
<TablePaginationServer
|
|
531
554
|
{pageIndex}
|
|
@@ -13,9 +13,29 @@
|
|
|
13
13
|
export let filters;
|
|
14
14
|
export let updateTable;
|
|
15
15
|
export let pageIndex;
|
|
16
|
+
export let toStringFn: undefined | ((value: any) => string) = undefined;
|
|
16
17
|
|
|
17
18
|
// If the filter is applied and the displayed values are filtered
|
|
18
19
|
let active = false;
|
|
20
|
+
let type: string = 'string';
|
|
21
|
+
let isDate = false;
|
|
22
|
+
let dropdowns: {
|
|
23
|
+
option: FilterOptionsEnum;
|
|
24
|
+
value: string | number | Date | undefined;
|
|
25
|
+
}[] = [];
|
|
26
|
+
|
|
27
|
+
// Check the type of the column
|
|
28
|
+
$values.forEach((item) => {
|
|
29
|
+
if (item) {
|
|
30
|
+
type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
|
|
31
|
+
|
|
32
|
+
if (type === 'object') {
|
|
33
|
+
if ((toFilterableValueFn ? toFilterableValueFn(item) : item) instanceof Date) {
|
|
34
|
+
isDate = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
19
39
|
|
|
20
40
|
// Options for different types of values
|
|
21
41
|
const options = {
|
|
@@ -92,18 +112,14 @@
|
|
|
92
112
|
value: FilterOptionsEnum.b,
|
|
93
113
|
label: 'Is before'
|
|
94
114
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
// TODO: 'Not on' filter should be fixed on the server side
|
|
116
|
+
// {
|
|
117
|
+
// value: FilterOptionsEnum.no,
|
|
118
|
+
// label: 'Is not on'
|
|
119
|
+
// }
|
|
99
120
|
]
|
|
100
121
|
};
|
|
101
122
|
|
|
102
|
-
let dropdowns: {
|
|
103
|
-
option: FilterOptionsEnum;
|
|
104
|
-
value: string | number | Date | undefined;
|
|
105
|
-
}[] = [];
|
|
106
|
-
|
|
107
123
|
// Unique ID for the column filter popup
|
|
108
124
|
const popupId = `${tableId}-${id}`;
|
|
109
125
|
// Popup config
|
|
@@ -113,24 +129,25 @@
|
|
|
113
129
|
placement: 'bottom-start'
|
|
114
130
|
};
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
let isDate = false;
|
|
118
|
-
// Check the type of the column
|
|
119
|
-
$values.forEach((item) => {
|
|
120
|
-
if (item) {
|
|
121
|
-
type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
|
|
132
|
+
const stringValues = $values.map((item) => (toStringFn ? toStringFn(item) : item));
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
const missingValues = stringValues.reduce((acc, item, index) => {
|
|
135
|
+
acc[typeof item === 'string' ? item.toLowerCase() : item] = $values[index];
|
|
136
|
+
return acc;
|
|
137
|
+
}, {});
|
|
138
|
+
|
|
139
|
+
const getMissingValue = (value: string) => {
|
|
140
|
+
return Object.keys(missingValues).includes(value.toLowerCase())
|
|
141
|
+
? missingValues[value.toLowerCase()]
|
|
142
|
+
: value;
|
|
143
|
+
};
|
|
130
144
|
|
|
131
145
|
const optionChangeHandler = (e, index) => {
|
|
132
146
|
delete $filters[id][dropdowns[index].option];
|
|
133
|
-
$filters[id] = {
|
|
147
|
+
$filters[id] = {
|
|
148
|
+
...$filters[id],
|
|
149
|
+
[e.target.value]: getMissingValue(dropdowns[index].value as string)
|
|
150
|
+
};
|
|
134
151
|
$filters = $filters;
|
|
135
152
|
|
|
136
153
|
dropdowns[index] = {
|
|
@@ -142,17 +159,18 @@
|
|
|
142
159
|
const valueChangeHandler = (e, index) => {
|
|
143
160
|
dropdowns[index] = {
|
|
144
161
|
...dropdowns[index],
|
|
145
|
-
value:
|
|
146
|
-
type === 'number'
|
|
147
|
-
? +e.target.value
|
|
148
|
-
: type === 'date'
|
|
149
|
-
? new Date(e.target.value)
|
|
150
|
-
: e.target.value
|
|
162
|
+
value: type === 'date' ? new Date(e.target.value) : e.target.value
|
|
151
163
|
};
|
|
152
164
|
|
|
153
165
|
$filters = {
|
|
154
166
|
...$filters,
|
|
155
|
-
[id]: {
|
|
167
|
+
[id]: {
|
|
168
|
+
...$filters[id],
|
|
169
|
+
[dropdowns[index].option]:
|
|
170
|
+
type === 'number'
|
|
171
|
+
? parseFloat(getMissingValue(e.target.value)) || undefined
|
|
172
|
+
: getMissingValue(e.target.value)
|
|
173
|
+
}
|
|
156
174
|
};
|
|
157
175
|
};
|
|
158
176
|
|
|
@@ -201,6 +219,8 @@
|
|
|
201
219
|
|
|
202
220
|
// Start by adding the default filter
|
|
203
221
|
$: addFilter(options[type][0].value, undefined);
|
|
222
|
+
|
|
223
|
+
$: console.log($filters);
|
|
204
224
|
</script>
|
|
205
225
|
|
|
206
226
|
<form class="">
|
|
@@ -258,14 +278,14 @@
|
|
|
258
278
|
{/if}
|
|
259
279
|
</div>
|
|
260
280
|
|
|
261
|
-
{#if type === 'number'}
|
|
281
|
+
<!-- {#if type === 'number'}
|
|
262
282
|
<input
|
|
263
283
|
type="number"
|
|
264
284
|
class="input p-1 border border-primary-500"
|
|
265
285
|
on:input={(e) => valueChangeHandler(e, index)}
|
|
266
286
|
bind:value={dropdown.value}
|
|
267
|
-
/>
|
|
268
|
-
{
|
|
287
|
+
/> -->
|
|
288
|
+
{#if type === 'number' || type === 'string'}
|
|
269
289
|
<input
|
|
270
290
|
type="text"
|
|
271
291
|
class="input p-1 border border-primary-500"
|
|
@@ -107,35 +107,79 @@ export const resetResize = (
|
|
|
107
107
|
}
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
export const missingValuesFn = (
|
|
111
|
-
|
|
110
|
+
export const missingValuesFn = (
|
|
111
|
+
key: number | string,
|
|
112
|
+
missingValues: { [key: string | number]: string }
|
|
113
|
+
) => {
|
|
114
|
+
const foundKey =
|
|
115
|
+
typeof key === 'number' && key.toString().includes('e')
|
|
116
|
+
? Object.keys(missingValues).find((item) => {
|
|
117
|
+
return (item as string).toLowerCase() === key.toString().toLowerCase();
|
|
118
|
+
})
|
|
119
|
+
: typeof key === 'string' && parseInt(key).toString().length !== key.length && new Date(key)
|
|
120
|
+
? Object.keys(missingValues).find(
|
|
121
|
+
(item) => new Date(item).getTime() === new Date(key).getTime()
|
|
122
|
+
)
|
|
123
|
+
: key in missingValues
|
|
124
|
+
? key
|
|
125
|
+
: undefined;
|
|
126
|
+
|
|
127
|
+
return foundKey ? missingValues[foundKey] : key;
|
|
112
128
|
};
|
|
113
129
|
|
|
114
|
-
export const convertServerColumns = (
|
|
130
|
+
export const convertServerColumns = (
|
|
131
|
+
serverColumns: ServerColumn[],
|
|
132
|
+
columns: Columns | undefined
|
|
133
|
+
) => {
|
|
115
134
|
const columnsConfig: Columns = {};
|
|
116
135
|
|
|
117
|
-
|
|
136
|
+
serverColumns.forEach((col) => {
|
|
118
137
|
let instructions = {};
|
|
119
138
|
|
|
120
139
|
if (col.instructions?.displayPattern) {
|
|
140
|
+
let dp = col.instructions.displayPattern;
|
|
141
|
+
|
|
142
|
+
// Swap 'm' and 'M' to match the backend date format
|
|
143
|
+
for (let i = 0; i < col.instructions.displayPattern.length; i++) {
|
|
144
|
+
if (col.instructions.displayPattern[i] === 'm') {
|
|
145
|
+
dp = `${dp.slice(0, i)}M${dp.slice(i + 1)}`;
|
|
146
|
+
} else if (col.instructions.displayPattern[i] === 'M') {
|
|
147
|
+
dp = `${dp.slice(0, i)}m${dp.slice(i + 1)}`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
121
151
|
instructions = {
|
|
122
|
-
toStringFn: (date:
|
|
123
|
-
|
|
124
|
-
|
|
152
|
+
toStringFn: (date: string) => {
|
|
153
|
+
if (col.instructions?.missingValues) {
|
|
154
|
+
const missingValue = missingValuesFn(date, col.instructions?.missingValues || {});
|
|
155
|
+
if (missingValue === date) {
|
|
156
|
+
return dateFormat(new Date(date), dp);
|
|
157
|
+
}
|
|
158
|
+
return missingValue;
|
|
159
|
+
} else {
|
|
160
|
+
return dateFormat(new Date(date), dp);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
toSortableValueFn: (date: string) => new Date(date).getTime(),
|
|
164
|
+
toFilterableValueFn: (date: string) => new Date(date)
|
|
125
165
|
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (col.instructions?.missingValues) {
|
|
166
|
+
} else if (col.instructions?.missingValues) {
|
|
129
167
|
instructions = {
|
|
130
168
|
...instructions,
|
|
131
169
|
toStringFn: (key) => missingValuesFn(key, col.instructions?.missingValues || {})
|
|
132
170
|
};
|
|
133
171
|
}
|
|
134
172
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
173
|
+
if (columns && col.column in columns) {
|
|
174
|
+
columnsConfig[col.column] = {
|
|
175
|
+
...columns[col.column],
|
|
176
|
+
instructions
|
|
177
|
+
};
|
|
178
|
+
} else {
|
|
179
|
+
columnsConfig[col.column] = {
|
|
180
|
+
instructions
|
|
181
|
+
};
|
|
182
|
+
}
|
|
139
183
|
});
|
|
140
184
|
|
|
141
185
|
return columnsConfig;
|
package/src/lib/models/Models.ts
CHANGED
|
@@ -167,7 +167,8 @@ export interface notificationStoreType {
|
|
|
167
167
|
|
|
168
168
|
// Table column type for server-side table
|
|
169
169
|
export type ServerColumn = {
|
|
170
|
-
column: string;
|
|
170
|
+
column: string; // column name on client side
|
|
171
|
+
key: string; // column name received from the server
|
|
171
172
|
exclude?: boolean; // false by default
|
|
172
173
|
instructions?: {
|
|
173
174
|
missingValues?: { [key: string | number]: string };
|