@bexis2/bexis2-core-ui 0.4.22 → 0.4.24

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.
@@ -0,0 +1,373 @@
1
+ import dateFormat from 'dateformat';
2
+ import { SvelteComponent } from 'svelte';
3
+ import type { Writable } from 'svelte/store';
4
+
5
+ import { Send, Receive } from '$models/Models';
6
+ import type { FilterOptionsEnum } from '$models/Enums';
7
+ import type { Columns, Filter, ServerColumn, ServerConfig } from '$models/Models';
8
+
9
+ // Function to determine minWidth for a column to simplify the logic in the HTML
10
+ export const minWidth = (id: string, columns: Columns | undefined) => {
11
+ if (columns && id in columns) {
12
+ return columns[id].minWidth ?? 0;
13
+ }
14
+ return 0;
15
+ };
16
+ // Function to determine fixedWidth for a column to simplify the logic in the HTML
17
+ export const fixedWidth = (id: string, columns: Columns | undefined) => {
18
+ if (columns && id in columns) {
19
+ return columns[id].fixedWidth ?? 0;
20
+ }
21
+ return 0;
22
+ };
23
+ // Function to create custom styles for the columns to simplify the logic in the HTML
24
+ export const cellStyle = (id: string, columns: Columns | undefined) => {
25
+ const minW = minWidth(id, columns);
26
+ const fixedW = fixedWidth(id, columns);
27
+ const styles: string[] = [];
28
+
29
+ // If minWidth is provided, add to styles
30
+ minW && styles.push(`min-width: ${minW}px`);
31
+ // If fixedWidth is provided, add to styles
32
+ fixedW && styles.push(`width: ${fixedW}px`);
33
+ // Create and return styles separated by ';'
34
+ return styles.join(';');
35
+ };
36
+ // Function to normalize the filters for back-end
37
+ export const normalizeFilters = (filters: {
38
+ [key: string]: { [key in FilterOptionsEnum]?: number | string | Date };
39
+ }) => {
40
+ let filter: Filter[] = [];
41
+
42
+ // Add filters to the request
43
+ Object.keys(filters).forEach((key) => {
44
+ Object.keys(filters[key])
45
+ .filter((k) => filters[key][k] !== undefined)
46
+ .forEach((k) => {
47
+ filter.push({
48
+ column: key,
49
+ filterBy: k as FilterOptionsEnum,
50
+ value: filters[key][k]
51
+ });
52
+ });
53
+ });
54
+
55
+ return filter;
56
+ };
57
+
58
+ export const exportAsCsv = (tableId: string, exportedData: string) => {
59
+ // Creating a hidden anchor element to download the CSV file
60
+ const anchor = document.createElement('a');
61
+ anchor.style.display = 'none';
62
+ anchor.href = `data:text/csv;charset=utf-8,${encodeURIComponent(exportedData)}`;
63
+ anchor.download = `${tableId}.csv`;
64
+ document.body.appendChild(anchor);
65
+ anchor.click();
66
+ document.body.removeChild(anchor);
67
+ };
68
+
69
+ export const jsonToCsv = (data: string): string => {
70
+ const json = JSON.parse(data);
71
+
72
+ if (json.length === 0) return '';
73
+
74
+ // Extract headers (keys)
75
+ const headers = Object.keys(json[0]);
76
+
77
+ // Escape and format a single cell
78
+ const escapeCsvCell = (value: any): string => {
79
+ if (value === null || value === undefined) return '';
80
+ let cell = String(value);
81
+ // Escape quotes by doubling them, and wrap the value in quotes if it contains special characters
82
+ if (/[",\n]/.test(cell)) {
83
+ cell = `"${cell.replace(/"/g, '""')}"`;
84
+ }
85
+ return cell;
86
+ };
87
+
88
+ // Create CSV rows
89
+ const rows = [
90
+ headers.join(','), // Header row
91
+ ...json.map((row) =>
92
+ headers.map(header => escapeCsvCell(row[header])).join(',')
93
+ ) // Data rows
94
+ ];
95
+
96
+ // Join rows with newlines
97
+ return rows.join('\n');
98
+ }
99
+
100
+ // Resetting the resized columns and/or rows
101
+ export const resetResize = (
102
+ headerRows: any,
103
+ pageRows: any,
104
+ tableId: string,
105
+ columns: Columns | undefined,
106
+ resizable: 'none' | 'rows' | 'columns' | 'both'
107
+ ) => {
108
+ // Run only if resizable is not none
109
+ if (resizable === 'columns' || resizable === 'both') {
110
+ headerRows.forEach((row) => {
111
+ row.cells.forEach((cell) => {
112
+ const minW = minWidth(cell.id, columns);
113
+ const fixedW = fixedWidth(cell.id, columns);
114
+ // If a fixedWidth is provided for a column, then reset the width to that value
115
+ fixedW &&
116
+ document
117
+ .getElementById(`th-${tableId}-${cell.id}`)
118
+ ?.style.setProperty('width', `${fixedW}px`);
119
+ // If a minWidth is provided for a column, then reset the width to that value
120
+ minW &&
121
+ document
122
+ .getElementById(`th-${tableId}-${cell.id}`)
123
+ ?.style.setProperty('width', `${minW}px`);
124
+ // If neither minWidth nor fixedWidth provided for a column, then reset the width to auto
125
+ !minW &&
126
+ !fixedW &&
127
+ document.getElementById(`th-${tableId}-${cell.id}`)?.style.setProperty('width', 'auto');
128
+ });
129
+ });
130
+ }
131
+
132
+ if (resizable === 'rows' || resizable === 'both') {
133
+ pageRows.forEach((row) => {
134
+ row.cells.forEach((cell) => {
135
+ // Reset all row heights to auto
136
+ document
137
+ .getElementById(`${tableId}-${cell.id}-${row.id}`)
138
+ ?.style.setProperty('height', 'auto');
139
+ });
140
+ });
141
+ }
142
+ };
143
+
144
+ export const missingValuesFn = (
145
+ key: number | string,
146
+ missingValues: { [key: string | number]: string }
147
+ ) => {
148
+ const foundKey =
149
+ typeof key === 'number' && key.toString().includes('e')
150
+ ? Object.keys(missingValues).find((item) => {
151
+ return (item as string).toLowerCase() === key.toString().toLowerCase();
152
+ })
153
+ : typeof key === 'string' && parseInt(key).toString().length !== key.length && new Date(key)
154
+ ? Object.keys(missingValues).find(
155
+ (item) => new Date(item).getTime() === new Date(key).getTime()
156
+ )
157
+ : key in missingValues
158
+ ? key
159
+ : undefined;
160
+
161
+ return foundKey ? missingValues[foundKey] : key;
162
+ };
163
+
164
+ // Function to update the server-side table data
165
+ export const updateTable = async (
166
+ pageSize: number,
167
+ pageIndex: number,
168
+ server: ServerConfig | undefined,
169
+ filters: {
170
+ [key: string]: { [key in FilterOptionsEnum]?: number | string | Date }
171
+ },
172
+ data: Writable<any[]>,
173
+ serverItems: Writable<number> | undefined,
174
+ columns: Columns | undefined,
175
+ dispatch: any
176
+ ) => {
177
+ const { baseUrl, entityId, versionId, sendModel = new Send() } = server ?? {};
178
+
179
+ if (!sendModel) throw new Error('Server-side configuration is missing');
180
+
181
+ sendModel.limit = pageSize;
182
+ sendModel.offset = pageSize * pageIndex;
183
+ sendModel.version = versionId || -1;
184
+ sendModel.id = entityId || -1;
185
+ sendModel.filter = normalizeFilters(filters);
186
+
187
+ let fetchData;
188
+
189
+ try {
190
+ fetchData = await fetch(baseUrl || '', {
191
+ headers: {
192
+ 'Content-Type': 'application/json'
193
+ },
194
+ method: 'POST',
195
+ body: JSON.stringify(sendModel)
196
+ });
197
+ } catch (error) {
198
+ throw new Error(`Network error: ${(error as Error).message}`);
199
+ }
200
+
201
+ if (!fetchData.ok) {
202
+ throw new Error('Failed to fetch data');
203
+ }
204
+
205
+ const response: Receive = await fetchData.json();
206
+
207
+ // Format server columns to the client columns
208
+ if (response.columns !== undefined) {
209
+ columns = convertServerColumns(response.columns, columns);
210
+
211
+ const clientCols = response.columns.reduce((acc, col) => {
212
+ acc[col.key] = col.column;
213
+ return acc;
214
+ }, {});
215
+
216
+ const tmpArr: any[] = [];
217
+
218
+ response.data.forEach((row, index) => {
219
+ const tmp: { [key: string]: any } = {};
220
+ Object.keys(row).forEach((key) => {
221
+ tmp[clientCols[key]] = row[key];
222
+ });
223
+ tmpArr.push(tmp);
224
+ });
225
+ dispatch('fetch', columns);
226
+ data.set(tmpArr);
227
+ }
228
+
229
+ serverItems?.set(response.count);
230
+
231
+ return response;
232
+ };
233
+
234
+ export const convertServerColumns = (
235
+ serverColumns: ServerColumn[],
236
+ columns: Columns | undefined
237
+ ) => {
238
+ const columnsConfig: Columns = {};
239
+
240
+ serverColumns.forEach((col) => {
241
+ let instructions = {};
242
+
243
+ if (col.instructions?.displayPattern) {
244
+ let dp = col.instructions.displayPattern;
245
+
246
+ // Swap 'm' and 'M' to match the backend date format
247
+ for (let i = 0; i < col.instructions.displayPattern.length; i++) {
248
+ if (col.instructions.displayPattern[i] === 'm') {
249
+ dp = `${dp.slice(0, i)}M${dp.slice(i + 1)}`;
250
+ } else if (col.instructions.displayPattern[i] === 'M') {
251
+ dp = `${dp.slice(0, i)}m${dp.slice(i + 1)}`;
252
+ }
253
+ }
254
+
255
+ instructions = {
256
+ toStringFn: (date: string) => {
257
+ if (col.instructions?.missingValues) {
258
+ const missingValue = missingValuesFn(date, col.instructions?.missingValues || {});
259
+ if (missingValue === date) {
260
+ return dateFormat(new Date(date), dp);
261
+ }
262
+ return missingValue;
263
+ } else {
264
+ return dateFormat(new Date(date), dp);
265
+ }
266
+ },
267
+ toSortableValueFn: (date: string) => new Date(date).getTime(),
268
+ toFilterableValueFn: (date: string) => new Date(date)
269
+ };
270
+ } else if (col.instructions?.missingValues) {
271
+ instructions = {
272
+ ...instructions,
273
+ toStringFn: (key) => missingValuesFn(key, col.instructions?.missingValues || {})
274
+ };
275
+ }
276
+
277
+ if (columns && col.column in columns) {
278
+ columnsConfig[col.column] = {
279
+ ...columns[col.column],
280
+ instructions
281
+ };
282
+ } else {
283
+ columnsConfig[col.column] = {
284
+ instructions
285
+ };
286
+ }
287
+ });
288
+
289
+ return columnsConfig;
290
+ };
291
+
292
+ // Calculates the maximum height of the cells in each row
293
+ export const getMaxCellHeightInRow = (
294
+ tableRef: HTMLTableElement,
295
+ resizable: 'columns' | 'rows' | 'none' | 'both',
296
+ optionsComponent: typeof SvelteComponent | undefined,
297
+ rowHeights: Writable<{ [key: number]: { max: number; min: number } }>,
298
+ tableId: string,
299
+ rowHeight: number | null
300
+ ) => {
301
+ if (!tableRef || resizable === 'columns' || resizable === 'none') return;
302
+
303
+ tableRef.querySelectorAll('tbody tr').forEach((row, index) => {
304
+ const cells = row.querySelectorAll('td');
305
+
306
+ let maxHeight = optionsComponent ? 56 : 44;
307
+ let minHeight = optionsComponent ? 56 : 44;
308
+
309
+ cells.forEach((cell) => {
310
+ const cellHeight = cell.getBoundingClientRect().height;
311
+ // + 2 pixels for rendering borders correctly
312
+ if (cellHeight > maxHeight) {
313
+ maxHeight = cellHeight + 2;
314
+ }
315
+ if (cellHeight < minHeight) {
316
+ minHeight = cellHeight + 2;
317
+ }
318
+ });
319
+
320
+ rowHeights.update((rh) => {
321
+ const id = +row.id.split(`${tableId}-row-`)[1];
322
+ return {
323
+ ...rh,
324
+ [id]: {
325
+ max: maxHeight - 24,
326
+ min: Math.max(minHeight - 24, rowHeight ?? 20)
327
+ }
328
+ };
329
+ });
330
+ });
331
+ };
332
+
333
+ // Calculates the minimum width of the cells in each column
334
+ export const getMinCellWidthInColumn = (
335
+ tableRef: HTMLTableElement,
336
+ colWidths: Writable<number[]>,
337
+ headerRowsLength: number,
338
+ resizable: 'columns' | 'rows' | 'none' | 'both'
339
+ ) => {
340
+ if (!tableRef || resizable === 'rows' || resizable === 'none') return;
341
+
342
+ // Initialize the column widths if they are not already initialized
343
+ colWidths.update((cw) => {
344
+ if (cw.length === 0) {
345
+ return Array.from({ length: headerRowsLength }, () => 100);
346
+ }
347
+ return cw;
348
+ });
349
+
350
+ colWidths.update((cw) => {
351
+ tableRef?.querySelectorAll('thead tr th span').forEach((cell, index) => {
352
+ // + 12 pixels for padding and + 32 pixels for filter icon
353
+ // If the column width is 100, which means it has not been initialized, then calculate the width
354
+ cw[index] = cw[index] === 100 ? cell.getBoundingClientRect().width + 12 + 32 : cw[index];
355
+ });
356
+ return cw;
357
+ });
358
+ };
359
+
360
+ export const getResizeStyles = (
361
+ rowHeights: { [key: number]: { max: number; min: number } },
362
+ id: string | number,
363
+ index: number
364
+ ) => {
365
+ return `
366
+ min-height: ${rowHeights && rowHeights[+id] ? `${rowHeights[+id].min}px` : 'auto'};
367
+ max-height: ${index !== 0 && rowHeights && rowHeights[+id]
368
+ ? `${rowHeights[+id].max}px`
369
+ : 'auto'
370
+ };
371
+ height: ${rowHeights && rowHeights[+id] ? `${rowHeights[+id].min}px` : 'auto'};
372
+ `;
373
+ }
@@ -71,7 +71,7 @@
71
71
  }
72
72
 
73
73
  if (!complexSource && !complexTarget && isLoaded && !isMulti) {
74
- console.log('🚀 ~ updateTarget ~ selection:', selection);
74
+ //console.log('🚀 ~ updateTarget ~ selection:', selection);
75
75
  if (selection) {
76
76
  target = selection.value;
77
77
  }
@@ -117,6 +117,7 @@ export interface TableConfig<T> {
117
117
  id: string;
118
118
  data: Writable<T[]>;
119
119
  resizable?: 'none' | 'rows' | 'columns' | 'both'; // none by default
120
+ showColumnsMenu?: boolean; // false by default
120
121
  toggle?: boolean; // false by default
121
122
  search?: boolean; // true by default
122
123
  fitToScreen?: boolean; // true by default
@@ -126,6 +127,7 @@ export interface TableConfig<T> {
126
127
  exportable?: boolean; // false by default
127
128
  pageSizes?: number[]; // [5, 10, 20, 50, 100] by default
128
129
  defaultPageSize?: number; // 10 by default
130
+ pageIndexStringType?: 'items' | 'pages'; // pages by default
129
131
  optionsComponent?: typeof SvelteComponent;
130
132
 
131
133
  server?: ServerConfig;
@@ -1,14 +0,0 @@
1
- import type { FilterOptionsEnum } from '../../models/Enums';
2
- import type { Columns, Filter, ServerColumn } from '../../models/Models';
3
- export declare const minWidth: (id: string, columns: Columns | undefined) => number;
4
- export declare const fixedWidth: (id: string, columns: Columns | undefined) => number;
5
- export declare const cellStyle: (id: string, columns: Columns | undefined) => string;
6
- export declare const normalizeFilters: (filters: {
7
- [key: string]: { [key in FilterOptionsEnum]?: number | string | Date; };
8
- }) => Filter[];
9
- export declare const exportAsCsv: (tableId: string, exportedData: string) => void;
10
- export declare const resetResize: (headerRows: any, pageRows: any, tableId: string, columns: Columns | undefined, resizable: "none" | "rows" | "columns" | "both") => void;
11
- export declare const missingValuesFn: (key: number | string, missingValues: {
12
- [key: string | number]: string;
13
- }) => string | number;
14
- export declare const convertServerColumns: (serverColumns: ServerColumn[], columns: Columns | undefined) => Columns;
@@ -1,186 +0,0 @@
1
- import dateFormat from 'dateformat';
2
-
3
- import type { FilterOptionsEnum } from '$models/Enums';
4
- import type { Columns, Filter, ServerColumn } from '$models/Models';
5
-
6
- // Function to determine minWidth for a column to simplify the logic in the HTML
7
- export const minWidth = (id: string, columns: Columns | undefined) => {
8
- if (columns && id in columns) {
9
- return columns[id].minWidth ?? 0;
10
- }
11
- return 0;
12
- };
13
- // Function to determine fixedWidth for a column to simplify the logic in the HTML
14
- export const fixedWidth = (id: string, columns: Columns | undefined) => {
15
- if (columns && id in columns) {
16
- return columns[id].fixedWidth ?? 0;
17
- }
18
- return 0;
19
- };
20
- // Function to create custom styles for the columns to simplify the logic in the HTML
21
- export const cellStyle = (id: string, columns: Columns | undefined) => {
22
- const minW = minWidth(id, columns);
23
- const fixedW = fixedWidth(id, columns);
24
- const styles: string[] = [];
25
-
26
- // If minWidth is provided, add to styles
27
- minW && styles.push(`min-width: ${minW}px`);
28
- // If fixedWidth is provided, add to styles
29
- fixedW && styles.push(`width: ${fixedW}px`);
30
- // Create and return styles separated by ';'
31
- return styles.join(';');
32
- };
33
- // Function to normalize the filters for back-end
34
- export const normalizeFilters = (filters: {
35
- [key: string]: { [key in FilterOptionsEnum]?: number | string | Date };
36
- }) => {
37
- let filter: Filter[] = [];
38
-
39
- // Add filters to the request
40
- Object.keys(filters).forEach((key) => {
41
- Object.keys(filters[key])
42
- .filter((k) => filters[key][k] !== undefined)
43
- .forEach((k) => {
44
- filter.push({
45
- column: key,
46
- filterBy: k as FilterOptionsEnum,
47
- value: filters[key][k]
48
- });
49
- });
50
- });
51
-
52
- return filter;
53
- };
54
-
55
- export const exportAsCsv = (tableId: string, exportedData: string) => {
56
- // Creating a hidden anchor element to download the CSV file
57
- const anchor = document.createElement('a');
58
- anchor.style.display = 'none';
59
- anchor.href = `data:text/csv;charset=utf-8,${encodeURIComponent(exportedData)}`;
60
- anchor.download = `${tableId}.csv`;
61
- document.body.appendChild(anchor);
62
- anchor.click();
63
- document.body.removeChild(anchor);
64
- };
65
-
66
- // Resetting the resized columns and/or rows
67
- export const resetResize = (
68
- headerRows: any,
69
- pageRows: any,
70
- tableId: string,
71
- columns: Columns | undefined,
72
- resizable: 'none' | 'rows' | 'columns' | 'both'
73
- ) => {
74
- // Run only if resizable is not none
75
- if (resizable === 'columns' || resizable === 'both') {
76
- headerRows.forEach((row) => {
77
- row.cells.forEach((cell) => {
78
- const minW = minWidth(cell.id, columns);
79
- const fixedW = fixedWidth(cell.id, columns);
80
- // If a fixedWidth is provided for a column, then reset the width to that value
81
- fixedW &&
82
- document
83
- .getElementById(`th-${tableId}-${cell.id}`)
84
- ?.style.setProperty('width', `${fixedW}px`);
85
- // If a minWidth is provided for a column, then reset the width to that value
86
- minW &&
87
- document
88
- .getElementById(`th-${tableId}-${cell.id}`)
89
- ?.style.setProperty('width', `${minW}px`);
90
- // If neither minWidth nor fixedWidth provided for a column, then reset the width to auto
91
- !minW &&
92
- !fixedW &&
93
- document.getElementById(`th-${tableId}-${cell.id}`)?.style.setProperty('width', 'auto');
94
- });
95
- });
96
- }
97
-
98
- if (resizable === 'rows' || resizable === 'both') {
99
- pageRows.forEach((row) => {
100
- row.cells.forEach((cell) => {
101
- // Reset all row heights to auto
102
- document
103
- .getElementById(`${tableId}-${cell.id}-${row.id}`)
104
- ?.style.setProperty('height', 'auto');
105
- });
106
- });
107
- }
108
- };
109
-
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;
128
- };
129
-
130
- export const convertServerColumns = (
131
- serverColumns: ServerColumn[],
132
- columns: Columns | undefined
133
- ) => {
134
- const columnsConfig: Columns = {};
135
-
136
- serverColumns.forEach((col) => {
137
- let instructions = {};
138
-
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
-
151
- instructions = {
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)
165
- };
166
- } else if (col.instructions?.missingValues) {
167
- instructions = {
168
- ...instructions,
169
- toStringFn: (key) => missingValuesFn(key, col.instructions?.missingValues || {})
170
- };
171
- }
172
-
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
- }
183
- });
184
-
185
- return columnsConfig;
186
- };