@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,34 @@
1
+ import { SvelteComponent } from 'svelte';
2
+ import type { Writable } from 'svelte/store';
3
+ import { Receive } from '../../models/Models';
4
+ import type { FilterOptionsEnum } from '../../models/Enums';
5
+ import type { Columns, Filter, ServerColumn, ServerConfig } from '../../models/Models';
6
+ export declare const minWidth: (id: string, columns: Columns | undefined) => number;
7
+ export declare const fixedWidth: (id: string, columns: Columns | undefined) => number;
8
+ export declare const cellStyle: (id: string, columns: Columns | undefined) => string;
9
+ export declare const normalizeFilters: (filters: {
10
+ [key: string]: { [key in FilterOptionsEnum]?: number | string | Date; };
11
+ }) => Filter[];
12
+ export declare const exportAsCsv: (tableId: string, exportedData: string) => void;
13
+ export declare const jsonToCsv: (data: string) => string;
14
+ export declare const resetResize: (headerRows: any, pageRows: any, tableId: string, columns: Columns | undefined, resizable: "none" | "rows" | "columns" | "both") => void;
15
+ export declare const missingValuesFn: (key: number | string, missingValues: {
16
+ [key: string | number]: string;
17
+ }) => string | number;
18
+ export declare const updateTable: (pageSize: number, pageIndex: number, server: ServerConfig | undefined, filters: {
19
+ [key: string]: { [key in FilterOptionsEnum]?: number | string | Date; };
20
+ }, data: Writable<any[]>, serverItems: Writable<number> | undefined, columns: Columns | undefined, dispatch: any) => Promise<Receive>;
21
+ export declare const convertServerColumns: (serverColumns: ServerColumn[], columns: Columns | undefined) => Columns;
22
+ export declare const getMaxCellHeightInRow: (tableRef: HTMLTableElement, resizable: "columns" | "rows" | "none" | "both", optionsComponent: typeof SvelteComponent | undefined, rowHeights: Writable<{
23
+ [key: number]: {
24
+ max: number;
25
+ min: number;
26
+ };
27
+ }>, tableId: string, rowHeight: number | null) => void;
28
+ export declare const getMinCellWidthInColumn: (tableRef: HTMLTableElement, colWidths: Writable<number[]>, headerRowsLength: number, resizable: "columns" | "rows" | "none" | "both") => void;
29
+ export declare const getResizeStyles: (rowHeights: {
30
+ [key: number]: {
31
+ max: number;
32
+ min: number;
33
+ };
34
+ }, id: string | number, index: number) => string;
@@ -1,4 +1,6 @@
1
1
  import dateFormat from 'dateformat';
2
+ import { SvelteComponent } from 'svelte';
3
+ import { Send, Receive } from '../../models/Models';
2
4
  // Function to determine minWidth for a column to simplify the logic in the HTML
3
5
  export const minWidth = (id, columns) => {
4
6
  if (columns && id in columns) {
@@ -52,6 +54,31 @@ export const exportAsCsv = (tableId, exportedData) => {
52
54
  anchor.click();
53
55
  document.body.removeChild(anchor);
54
56
  };
57
+ export const jsonToCsv = (data) => {
58
+ const json = JSON.parse(data);
59
+ if (json.length === 0)
60
+ return '';
61
+ // Extract headers (keys)
62
+ const headers = Object.keys(json[0]);
63
+ // Escape and format a single cell
64
+ const escapeCsvCell = (value) => {
65
+ if (value === null || value === undefined)
66
+ return '';
67
+ let cell = String(value);
68
+ // Escape quotes by doubling them, and wrap the value in quotes if it contains special characters
69
+ if (/[",\n]/.test(cell)) {
70
+ cell = `"${cell.replace(/"/g, '""')}"`;
71
+ }
72
+ return cell;
73
+ };
74
+ // Create CSV rows
75
+ const rows = [
76
+ headers.join(','), // Header row
77
+ ...json.map((row) => headers.map(header => escapeCsvCell(row[header])).join(',')) // Data rows
78
+ ];
79
+ // Join rows with newlines
80
+ return rows.join('\n');
81
+ };
55
82
  // Resetting the resized columns and/or rows
56
83
  export const resetResize = (headerRows, pageRows, tableId, columns, resizable) => {
57
84
  // Run only if resizable is not none
@@ -100,6 +127,54 @@ export const missingValuesFn = (key, missingValues) => {
100
127
  : undefined;
101
128
  return foundKey ? missingValues[foundKey] : key;
102
129
  };
130
+ // Function to update the server-side table data
131
+ export const updateTable = async (pageSize, pageIndex, server, filters, data, serverItems, columns, dispatch) => {
132
+ const { baseUrl, entityId, versionId, sendModel = new Send() } = server ?? {};
133
+ if (!sendModel)
134
+ throw new Error('Server-side configuration is missing');
135
+ sendModel.limit = pageSize;
136
+ sendModel.offset = pageSize * pageIndex;
137
+ sendModel.version = versionId || -1;
138
+ sendModel.id = entityId || -1;
139
+ sendModel.filter = normalizeFilters(filters);
140
+ let fetchData;
141
+ try {
142
+ fetchData = await fetch(baseUrl || '', {
143
+ headers: {
144
+ 'Content-Type': 'application/json'
145
+ },
146
+ method: 'POST',
147
+ body: JSON.stringify(sendModel)
148
+ });
149
+ }
150
+ catch (error) {
151
+ throw new Error(`Network error: ${error.message}`);
152
+ }
153
+ if (!fetchData.ok) {
154
+ throw new Error('Failed to fetch data');
155
+ }
156
+ const response = await fetchData.json();
157
+ // Format server columns to the client columns
158
+ if (response.columns !== undefined) {
159
+ columns = convertServerColumns(response.columns, columns);
160
+ const clientCols = response.columns.reduce((acc, col) => {
161
+ acc[col.key] = col.column;
162
+ return acc;
163
+ }, {});
164
+ const tmpArr = [];
165
+ response.data.forEach((row, index) => {
166
+ const tmp = {};
167
+ Object.keys(row).forEach((key) => {
168
+ tmp[clientCols[key]] = row[key];
169
+ });
170
+ tmpArr.push(tmp);
171
+ });
172
+ dispatch('fetch', columns);
173
+ data.set(tmpArr);
174
+ }
175
+ serverItems?.set(response.count);
176
+ return response;
177
+ };
103
178
  export const convertServerColumns = (serverColumns, columns) => {
104
179
  const columnsConfig = {};
105
180
  serverColumns.forEach((col) => {
@@ -152,3 +227,62 @@ export const convertServerColumns = (serverColumns, columns) => {
152
227
  });
153
228
  return columnsConfig;
154
229
  };
230
+ // Calculates the maximum height of the cells in each row
231
+ export const getMaxCellHeightInRow = (tableRef, resizable, optionsComponent, rowHeights, tableId, rowHeight) => {
232
+ if (!tableRef || resizable === 'columns' || resizable === 'none')
233
+ return;
234
+ tableRef.querySelectorAll('tbody tr').forEach((row, index) => {
235
+ const cells = row.querySelectorAll('td');
236
+ let maxHeight = optionsComponent ? 56 : 44;
237
+ let minHeight = optionsComponent ? 56 : 44;
238
+ cells.forEach((cell) => {
239
+ const cellHeight = cell.getBoundingClientRect().height;
240
+ // + 2 pixels for rendering borders correctly
241
+ if (cellHeight > maxHeight) {
242
+ maxHeight = cellHeight + 2;
243
+ }
244
+ if (cellHeight < minHeight) {
245
+ minHeight = cellHeight + 2;
246
+ }
247
+ });
248
+ rowHeights.update((rh) => {
249
+ const id = +row.id.split(`${tableId}-row-`)[1];
250
+ return {
251
+ ...rh,
252
+ [id]: {
253
+ max: maxHeight - 24,
254
+ min: Math.max(minHeight - 24, rowHeight ?? 20)
255
+ }
256
+ };
257
+ });
258
+ });
259
+ };
260
+ // Calculates the minimum width of the cells in each column
261
+ export const getMinCellWidthInColumn = (tableRef, colWidths, headerRowsLength, resizable) => {
262
+ if (!tableRef || resizable === 'rows' || resizable === 'none')
263
+ return;
264
+ // Initialize the column widths if they are not already initialized
265
+ colWidths.update((cw) => {
266
+ if (cw.length === 0) {
267
+ return Array.from({ length: headerRowsLength }, () => 100);
268
+ }
269
+ return cw;
270
+ });
271
+ colWidths.update((cw) => {
272
+ tableRef?.querySelectorAll('thead tr th span').forEach((cell, index) => {
273
+ // + 12 pixels for padding and + 32 pixels for filter icon
274
+ // If the column width is 100, which means it has not been initialized, then calculate the width
275
+ cw[index] = cw[index] === 100 ? cell.getBoundingClientRect().width + 12 + 32 : cw[index];
276
+ });
277
+ return cw;
278
+ });
279
+ };
280
+ export const getResizeStyles = (rowHeights, id, index) => {
281
+ return `
282
+ min-height: ${rowHeights && rowHeights[+id] ? `${rowHeights[+id].min}px` : 'auto'};
283
+ max-height: ${index !== 0 && rowHeights && rowHeights[+id]
284
+ ? `${rowHeights[+id].max}px`
285
+ : 'auto'};
286
+ height: ${rowHeights && rowHeights[+id] ? `${rowHeights[+id].min}px` : 'auto'};
287
+ `;
288
+ };
@@ -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
  }
@@ -90,6 +90,7 @@ export interface TableConfig<T> {
90
90
  id: string;
91
91
  data: Writable<T[]>;
92
92
  resizable?: 'none' | 'rows' | 'columns' | 'both';
93
+ showColumnsMenu?: boolean;
93
94
  toggle?: boolean;
94
95
  search?: boolean;
95
96
  fitToScreen?: boolean;
@@ -99,6 +100,7 @@ export interface TableConfig<T> {
99
100
  exportable?: boolean;
100
101
  pageSizes?: number[];
101
102
  defaultPageSize?: number;
103
+ pageIndexStringType?: 'items' | 'pages';
102
104
  optionsComponent?: typeof SvelteComponent;
103
105
  server?: ServerConfig;
104
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bexis2/bexis2-core-ui",
3
- "version": "0.4.22",
3
+ "version": "0.4.24",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -191,10 +191,14 @@
191
191
  hyphenOpacity="opacity-0"
192
192
  bind:checked={selectedGroups[group.name]}
193
193
  bind:group={selectedGroups}
194
+ regionSymbol="shrink-0"
194
195
  >
195
- <p class="font-semibold whitespace-nowrap">
196
- {group.displayName}{group.count !== undefined ? ` (${group.count})` : ''}
197
- </p>
196
+ <div class="grid grid-flow-col gap-2">
197
+ <p class="font-semibold whitespace-nowrap truncate" title={group.displayName}>
198
+ {group.displayName}
199
+ </p>
200
+ <span>{group.count !== undefined ? ` (${group.count})` : ''}</span>
201
+ </div>
198
202
 
199
203
  <svelte:fragment slot="children">
200
204
  <!-- If more than 5 choices, show the remaining in the Modal -->
@@ -210,9 +214,9 @@
210
214
  selection
211
215
  multiple
212
216
  >
213
- <div class="flex gap-2">
214
- <p class="w-max grow truncate">
215
- <span title={item.displayName}>{item.displayName}</span>
217
+ <div class="grid grid-flow-col gap-2">
218
+ <p class="truncate">
219
+ <span class="" title={item.displayName}>{item.displayName}</span>
216
220
  </p>
217
221
  <span>({item.count})</span>
218
222
  </div>
@@ -237,9 +241,9 @@
237
241
  selection
238
242
  multiple
239
243
  >
240
- <div class="flex gap-2">
241
- <p class="w-max grow truncate">
242
- <span title={item.displayName}>{item.displayName}</span>
244
+ <div class="grid grid-flow-col gap-2">
245
+ <p class="truncate">
246
+ <span class="" title={item.displayName}>{item.displayName}</span>
243
247
  </p>
244
248
  <span>({item.count})</span>
245
249
  </div>
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import Fa from 'svelte-fa';
3
+ import { faEye } from '@fortawesome/free-solid-svg-icons';
2
4
  import { popup } from '@skeletonlabs/skeleton';
3
5
  import type { PopupSettings } from '@skeletonlabs/skeleton';
4
6
 
@@ -8,29 +10,55 @@
8
10
  const popupCombobox: PopupSettings = {
9
11
  event: 'click',
10
12
  target: `${tableId}-columns-menu`,
11
- placement: 'bottom'
13
+ placement: 'bottom',
14
+ closeQuery: ''
15
+ };
16
+
17
+ const selectAll = () => {
18
+ columns = columns.map((column) => ({ ...column, visible: true }));
19
+ };
20
+
21
+ const deselectAll = () => {
22
+ columns = columns.map((column) => ({ ...column, visible: false }));
23
+ columns[0].visible = true;
12
24
  };
13
25
  </script>
14
26
 
15
27
  <button
16
28
  type="button"
17
- title="Hide or show columns"
18
- class="btn btn-sm variant-filled-primary rounded-full order-last"
29
+ class="btn btn-sm variant-filled-primary rounded-full order-last gap-2"
19
30
  aria-label="Open menu to hide/show columns"
20
- use:popup={popupCombobox}>Columns</button
31
+ use:popup={popupCombobox}><Fa icon={faEye} /> Columns</button
21
32
  >
22
-
23
33
  <div
24
- class="bg-white dark:bg-surface-500 p-4 rounded-md shadow-md z-10"
34
+ class="bg-white dark:bg-surface-500 p-4 px-5 rounded-md shadow-md z-10 border border-primary-500"
25
35
  data-popup="{tableId}-columns-menu"
26
36
  >
37
+ <div class="flex items-center gap-4 pb-5 grow justify-between">
38
+ <button
39
+ on:click|preventDefault={selectAll}
40
+ type="button"
41
+ class="btn p-0 text-sm grow underline text-primary-600"
42
+ >
43
+ Select All
44
+ </button>
45
+ <div class="border border-r border-neutral-200 h-6" />
46
+ <button
47
+ on:click|preventDefault={deselectAll}
48
+ type="button"
49
+ class="btn p-0 text-sm grow underline text-neutral-500"
50
+ >
51
+ Deselect All
52
+ </button>
53
+ </div>
27
54
  {#each columns as column}
28
55
  <div class="flex gap-3 items-center">
29
56
  <label for={column.id} class="cursor-pointer" title={column.label}></label>
30
57
  <input
31
58
  aria-label={`${column.visible ? 'Hide' : 'Show'} ${column.label} column`}
32
59
  type="checkbox"
33
- id = {column.id}
60
+ class="checkbox"
61
+ id={column.id}
34
62
  bind:checked={column.visible}
35
63
  title={`${column.visible ? 'Hide' : 'Show'} ${column.label} column`}
36
64
  disabled={columns.filter((c) => c.visible).length === 1 && column.visible}
@@ -39,5 +67,5 @@
39
67
  </div>
40
68
  {/each}
41
69
 
42
- <div class="arrow bg-white dark:bg-surface-500" />
70
+ <div class="arrow bg-white dark:bg-surface-500 border-l border-t border-primary-500" />
43
71
  </div>