Skip to content

Commit b58f4b8

Browse files
authored
Merge pull request #84 from BEXIS2/table
Server-side searching and enhancements
2 parents 59064ac + fcd107a commit b58f4b8

File tree

6 files changed

+128
-57
lines changed

6 files changed

+128
-57
lines changed

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"@fortawesome/free-solid-svg-icons": "^6.5.1",
7474
"axios": "^1.6.7",
7575
"codemirror": "^6.0.1",
76+
"dateformat": "^5.0.3",
7677
"delay": "^6.0.0",
7778
"dotenv": "^16.4.5",
7879
"eslint4b-prebuilt": "^6.7.2",

src/lib/components/Table/TableContent.svelte

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
2020
storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
2121
22+
import Spinner from '../page/Spinner.svelte';
2223
import TableFilter from './TableFilter.svelte';
2324
import TableFilterServer from './TableFilterServer.svelte';
2425
import TablePagination from './TablePagination.svelte';
@@ -61,6 +62,7 @@
6162
} = config;
6263
6364
let searchValue = '';
65+
let isFetching = false;
6466
6567
const filters = writable<{
6668
[key: string]: { [key in FilterOptionsEnum]?: number | string | Date };
@@ -194,7 +196,9 @@
194196
tableId,
195197
values,
196198
toFilterableValueFn,
197-
filters
199+
filters,
200+
toStringFn,
201+
pageIndex
198202
});
199203
}
200204
}
@@ -237,7 +241,8 @@
237241
id,
238242
tableId,
239243
values,
240-
filters
244+
filters,
245+
pageIndex
241246
});
242247
}
243248
}
@@ -280,22 +285,34 @@
280285
// Page configuration
281286
const { pageIndex, pageSize } = pluginStates.page;
282287
283-
// TODO: Add loading animation for server-side fetch requests
284288
const updateTable = async () => {
285289
sendModel.limit = $pageSize;
286290
sendModel.offset = $pageSize * $pageIndex;
287291
sendModel.version = versionId;
288292
sendModel.id = entityId;
289293
sendModel.filter = normalizeFilters($filters);
290294
291-
const fetchData = await fetch(URL, {
292-
headers: {
293-
'Content-Type': 'application/json',
294-
Authorization: `Bearer ${token}`
295-
},
296-
method: 'POST',
297-
body: JSON.stringify(sendModel)
298-
});
295+
let fetchData;
296+
297+
try {
298+
isFetching = true;
299+
fetchData = await fetch(URL, {
300+
headers: {
301+
'Content-Type': 'application/json',
302+
Authorization: `Bearer ${token}`
303+
},
304+
method: 'POST',
305+
body: JSON.stringify(sendModel)
306+
});
307+
} catch (error) {
308+
throw new Error(`Network error: ${(error as Error).message}`);
309+
} finally {
310+
isFetching = false;
311+
}
312+
313+
if (!fetchData.ok) {
314+
throw new Error('Failed to fetch data');
315+
}
299316
300317
const response: Receive = await fetchData.json();
301318
@@ -335,7 +352,13 @@
335352
<!-- Enable the search filter if table is not empty -->
336353
{#if $data.length > 0}
337354
{#if !serverSide}
338-
<div class="flex gap-2">
355+
<form
356+
class="flex gap-2"
357+
on:submit|preventDefault={() => {
358+
sendModel.q = searchValue;
359+
$filterValue = searchValue;
360+
}}
361+
>
339362
<div class="relative w-full flex items-center">
340363
<input
341364
class="input p-2 border border-primary-500"
@@ -348,18 +371,20 @@
348371
class="absolute right-3 items-center"
349372
on:click|preventDefault={() => {
350373
searchValue = '';
374+
sendModel.q = '';
351375
$filterValue = '';
352376
}}><Fa icon={faXmark} /></button
353377
>
354378
</div>
355379
<button
356-
type="button"
380+
type="submit"
357381
class="btn variant-filled-primary"
358382
on:click|preventDefault={() => {
359383
$filterValue = searchValue;
384+
sendModel.q = searchValue;
360385
}}>Search</button
361386
>
362-
</div>
387+
</form>
363388
{/if}
364389
<div class="flex justify-between items-center py-2 w-full">
365390
<div>
@@ -460,6 +485,8 @@
460485
</tr>
461486
</Subscribe>
462487
{/each}
488+
{:else if isFetching}
489+
<div class="p-10"><Spinner /></div>
463490
{:else}
464491
<!-- Table is empty -->
465492
<p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>

src/lib/components/Table/TableFilter.svelte

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,33 @@
1010
export let id;
1111
export let tableId;
1212
export let toFilterableValueFn: undefined | ((value: any) => any) = undefined;
13+
export let toStringFn: undefined | ((value: any) => string) = undefined;
1314
export let filterValue;
1415
export let filters;
16+
export let pageIndex;
1517
1618
// If the filter is applied and the displayed values are filtered
1719
let active = false;
20+
let type: string = 'string';
21+
let isDate = false; // Options for different types of values
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 (item instanceof Date) {
34+
isDate = true;
35+
}
36+
}
37+
}
38+
});
1839
19-
// Options for different types of values
2040
const options = {
2141
number: [
2242
{
@@ -98,11 +118,6 @@
98118
]
99119
};
100120
101-
let dropdowns: {
102-
option: FilterOptionsEnum;
103-
value: string | number | Date | undefined;
104-
}[] = [];
105-
106121
// Unique ID for the column filter popup
107122
const popupId = `${tableId}-${id}`;
108123
// Popup config
@@ -112,24 +127,39 @@
112127
placement: 'bottom-start'
113128
};
114129
115-
let type: string = 'string';
116-
let isDate = false;
117-
// Check the type of the column
118-
$values.forEach((item) => {
119-
if (item) {
120-
type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
130+
// Converted string values and missingValues mapping
131+
const stringValues =
132+
// type === 'number' ?
133+
$values.map((item) => (toStringFn ? toStringFn(item) : item));
134+
// : [];
121135
122-
if (type === 'object') {
123-
if (item instanceof Date) {
124-
isDate = true;
125-
}
126-
}
127-
}
128-
});
136+
const missingValues =
137+
// type === 'number' ?
138+
stringValues.reduce((acc, item, index) => {
139+
acc[typeof item === 'string' ? item.toLowerCase() : item] = $values[index];
140+
return acc;
141+
}, {});
142+
// : {};
143+
144+
const getMissingValue = (value: string) => {
145+
// if (type === 'number' ||) {
146+
return Object.keys(missingValues).includes(value.toLowerCase())
147+
? missingValues[value.toLowerCase()]
148+
: value;
149+
// }
150+
// return value;
151+
};
129152
130153
const optionChangeHandler = (e, index) => {
131154
delete $filters[id][dropdowns[index].option];
132-
$filters[id] = { ...$filters[id], [e.target.value]: dropdowns[index].value };
155+
$filters[id] = {
156+
...$filters[id],
157+
[e.target.value]:
158+
// type === 'number'
159+
// ?
160+
getMissingValue(dropdowns[index].value as string)
161+
// : dropdowns[index].value
162+
};
133163
$filters = $filters;
134164
135165
dropdowns[index] = {
@@ -141,17 +171,18 @@
141171
const valueChangeHandler = (e, index) => {
142172
dropdowns[index] = {
143173
...dropdowns[index],
144-
value:
145-
type === 'number'
146-
? +e.target.value
147-
: type === 'date'
148-
? new Date(e.target.value)
149-
: e.target.value
174+
value: type === 'date' ? new Date(e.target.value) : e.target.value
150175
};
151176
152177
$filters = {
153178
...$filters,
154-
[id]: { ...$filters[id], [dropdowns[index].option]: dropdowns[index].value }
179+
[id]: {
180+
...$filters[id],
181+
[dropdowns[index].option]:
182+
// type === 'number' ?
183+
getMissingValue(e.target.value)
184+
// : dropdowns[index].value
185+
}
155186
};
156187
};
157188
@@ -212,6 +243,7 @@
212243
addFilter(options[type][0].value, undefined);
213244
$filterValue = $filters[id];
214245
active = false;
246+
$pageIndex = 0;
215247
}}>Clear Filters</button
216248
>
217249

@@ -246,14 +278,7 @@
246278
{/if}
247279
</div>
248280

249-
{#if type === 'number'}
250-
<input
251-
type="number"
252-
class="input p-1 border border-primary-500"
253-
on:input={(e) => valueChangeHandler(e, index)}
254-
bind:value={dropdown.value}
255-
/>
256-
{:else if type === 'string'}
281+
{#if type === 'number' || type === 'string'}
257282
<input
258283
type="text"
259284
class="input p-1 border border-primary-500"
@@ -292,6 +317,7 @@
292317
class="btn variant-filled-primary btn-sm"
293318
type="button"
294319
on:click|preventDefault={() => {
320+
$pageIndex = 0;
295321
$filterValue = $filters[id];
296322
active = true;
297323
}}>Apply</button

src/lib/components/Table/shared.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import dateFormat from 'dateformat';
2+
13
import type { FilterOptionsEnum } from '$models/Enums';
24
import type { Columns, Filter, ServerColumn } from '$models/Models';
35

@@ -115,14 +117,13 @@ export const convertServerColumns = (columns: ServerColumn[]) => {
115117
columns.forEach((col) => {
116118
let instructions = {};
117119

118-
// if (col.instructions?.displayPattern) {
119-
// instructions = {
120-
// toStringFn: (date: Date) =>
121-
// date.toLocaleString('en-US', col.instructions?.displayPattern || {}),
122-
// toSortableValueFn: (date: Date) => date.getTime(),
123-
// toFilterableValueFn: (date: Date) => date
124-
// };
125-
// }
120+
if (col.instructions?.displayPattern) {
121+
instructions = {
122+
toStringFn: (date: Date) => dateFormat(date, col.instructions?.displayPattern || ''),
123+
toSortableValueFn: (date: Date) => date.getTime(),
124+
toFilterableValueFn: (date: Date) => date
125+
};
126+
}
126127

127128
if (col.instructions?.missingValues) {
128129
instructions = {

src/lib/models/Models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export class Send {
190190
id: number;
191191
limit: number;
192192
offset: number;
193+
q: string;
193194
version?: number;
194195
filter: Filter[];
195196
order: OrderBy[];
@@ -199,6 +200,7 @@ export class Send {
199200
this.limit = 10;
200201
this.offset = 0;
201202
this.version = 0;
203+
this.q = '';
202204
this.filter = [];
203205
this.order = [];
204206
}

0 commit comments

Comments
 (0)