quasar-ui-danx 0.4.1 → 0.4.3

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.
Files changed (80) hide show
  1. package/dist/danx.es.js +7234 -6741
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +11 -5
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -1
  7. package/src/components/ActionTable/ActionTable.vue +31 -43
  8. package/src/components/ActionTable/Columns/ActionTableColumn.vue +19 -18
  9. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +15 -14
  10. package/src/components/ActionTable/Filters/{FilterFieldList.vue → FilterList.vue} +26 -26
  11. package/src/components/ActionTable/Filters/FilterableField.vue +28 -31
  12. package/src/components/ActionTable/Filters/index.ts +2 -2
  13. package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +71 -0
  14. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +8 -13
  15. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +34 -33
  16. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +48 -44
  17. package/src/components/ActionTable/Form/Fields/NumberField.vue +60 -59
  18. package/src/components/ActionTable/Form/Fields/SelectField.vue +124 -138
  19. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +28 -33
  20. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +15 -15
  21. package/src/components/ActionTable/Form/Fields/SliderNumberField.vue +45 -0
  22. package/src/components/ActionTable/Form/Fields/TextField.vue +47 -66
  23. package/src/components/ActionTable/Form/Fields/index.ts +2 -0
  24. package/src/components/ActionTable/Form/RenderedForm.vue +50 -13
  25. package/src/components/ActionTable/Form/Utilities/MaxLengthCounter.vue +17 -0
  26. package/src/components/ActionTable/Form/Utilities/index.ts +1 -0
  27. package/src/components/ActionTable/Form/index.ts +1 -0
  28. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +22 -16
  29. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +11 -11
  30. package/src/components/ActionTable/listControls.ts +104 -166
  31. package/src/components/ActionTable/listHelpers.ts +2 -3
  32. package/src/components/ActionTable/tableColumns.ts +53 -77
  33. package/src/components/AuditHistory/AuditHistoryItemValue.vue +26 -26
  34. package/src/components/PanelsDrawer/PanelsDrawer.vue +17 -4
  35. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +6 -11
  36. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +20 -20
  37. package/src/components/Utility/Dialogs/ConfirmActionDialog.vue +39 -0
  38. package/src/components/Utility/Dialogs/ConfirmDialog.vue +57 -117
  39. package/src/components/Utility/Dialogs/DialogLayout.vue +77 -0
  40. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +42 -36
  41. package/src/components/Utility/Dialogs/InfoDialog.vue +40 -80
  42. package/src/components/Utility/Dialogs/index.ts +1 -0
  43. package/src/components/Utility/Files/FilePreview.vue +76 -73
  44. package/src/components/Utility/Layouts/ContentDrawer.vue +24 -31
  45. package/src/components/Utility/Tools/ActionVnode.vue +3 -3
  46. package/src/components/Utility/Tools/RenderVnode.vue +20 -11
  47. package/src/components/Utility/Transitions/MaxHeightTransition.vue +26 -0
  48. package/src/components/Utility/Transitions/index.ts +1 -0
  49. package/src/config/index.ts +36 -31
  50. package/src/helpers/FileUpload.ts +295 -297
  51. package/src/helpers/FlashMessages.ts +80 -71
  52. package/src/helpers/actions.ts +102 -82
  53. package/src/helpers/download.ts +189 -189
  54. package/src/helpers/downloadPdf.ts +55 -52
  55. package/src/helpers/formats.ts +151 -109
  56. package/src/helpers/index.ts +2 -0
  57. package/src/helpers/multiFileUpload.ts +72 -58
  58. package/src/helpers/objectStore.ts +52 -0
  59. package/src/helpers/request.ts +70 -51
  60. package/src/helpers/routes.ts +29 -0
  61. package/src/helpers/storage.ts +7 -3
  62. package/src/helpers/utils.ts +47 -29
  63. package/src/styles/quasar-reset.scss +94 -68
  64. package/src/styles/themes/danx/dialogs.scss +47 -0
  65. package/src/styles/themes/danx/forms.scss +18 -0
  66. package/src/styles/themes/danx/index.scss +4 -0
  67. package/src/types/actions.d.ts +43 -0
  68. package/src/types/config.d.ts +15 -0
  69. package/src/types/controls.d.ts +99 -0
  70. package/src/types/dialogs.d.ts +32 -0
  71. package/src/types/fields.d.ts +20 -0
  72. package/src/types/files.d.ts +54 -0
  73. package/src/types/formats.d.ts +4 -0
  74. package/src/{components/ActionTable/Form/form.d.ts → types/forms.d.ts} +6 -0
  75. package/src/types/index.d.ts +12 -0
  76. package/src/types/requests.d.ts +13 -0
  77. package/src/types/shared.d.ts +15 -0
  78. package/src/types/tables.d.ts +27 -0
  79. package/types/index.d.ts +1 -1
  80. /package/src/components/ActionTable/Filters/{FilterFieldItem.vue → FilterItem.vue} +0 -0
@@ -1,135 +1,40 @@
1
- import { computed, ComputedRef, Ref, ref, ShallowRef, shallowRef, VNode, watch } from "vue";
2
- import { ActionTargetItem, AnyObject, getItem, setItem, waitForRef } from "../../helpers";
1
+ import { computed, Ref, ref, shallowRef, watch } from "vue";
2
+ import { RouteLocationNormalizedLoaded, RouteParams, Router, useRoute, useRouter } from "vue-router";
3
+ import { getItem, setItem, storeObject, waitForRef } from "../../helpers";
4
+ import {
5
+ ActionController,
6
+ ActionTargetItem,
7
+ AnyObject,
8
+ FilterGroup,
9
+ ListControlsFilter,
10
+ ListControlsOptions,
11
+ ListControlsPagination,
12
+ PagedItems
13
+ } from "../../types";
3
14
  import { getFilterFromUrl } from "./listHelpers";
4
15
 
5
- export interface ActionController {
6
- name: string;
7
- label: string;
8
- pagedItems: Ref<PagedItems | null>;
9
- activeFilter: Ref<ListControlsFilter>;
10
- globalFilter: Ref<ListControlsFilter>;
11
- filterActiveCount: ComputedRef<number>;
12
- showFilters: Ref<boolean>;
13
- summary: ShallowRef<object | null>;
14
- filterFieldOptions: Ref<AnyObject>;
15
- selectedRows: ShallowRef<ActionTargetItem[]>;
16
- isLoadingList: Ref<boolean>;
17
- isLoadingFilters: Ref<boolean>;
18
- isLoadingSummary: Ref<boolean>;
19
- pager: ComputedRef<{
20
- perPage: number;
21
- page: number;
22
- filter: ListControlsFilter;
23
- sort: object[] | undefined;
24
- }>;
25
- pagination: ShallowRef<ListControlsPagination>;
26
- activeItem: ShallowRef<ActionTargetItem | null>;
27
- activePanel: ShallowRef<string | null>;
28
-
29
- // Actions
30
- initialize: () => void;
31
- loadSummary: () => Promise<void>;
32
- resetPaging: () => void;
33
- setPagination: (updated: ListControlsPagination) => void;
34
- setSelectedRows: (selection: ActionTargetItem[]) => void;
35
- loadList: () => Promise<void>;
36
- loadMore: (index: number, perPage?: number) => Promise<boolean>;
37
- refreshAll: () => Promise<void[]>;
38
- exportList: () => Promise<void>;
39
- setActiveItem: (item: ActionTargetItem | null) => void;
40
- getNextItem: (offset: number) => Promise<void>;
41
- activatePanel: (item: ActionTargetItem | null, panel: string | null) => void;
42
- setActiveFilter: (filter: ListControlsFilter) => void;
43
- applyFilterFromUrl: (url: string, filterFields: Ref<FilterGroup[]> | null) => void;
44
- setItemInList: (updatedItem: ActionTargetItem) => void;
45
- }
46
-
47
- export interface LabelValueItem {
48
- label: string;
49
- value: string | number | boolean;
50
- }
51
-
52
- export interface FilterField {
53
- name: string;
54
- label: string;
55
- type: string;
56
- options?: string[] | number[] | LabelValueItem[];
57
- inline?: boolean;
58
- }
59
-
60
- export interface FilterGroup {
61
- name?: string;
62
- flat?: boolean;
63
- fields: FilterField[];
64
- }
65
-
66
- export interface ActionPanel {
67
- name: string;
68
- label: string;
69
- category?: string;
70
- enabled: boolean | (() => boolean);
71
- tabVnode: () => VNode;
72
- vnode: () => VNode;
73
- }
74
-
75
- export interface ListControlsFilter {
76
- [key: string]: object | object[] | null | undefined | string | number | boolean;
77
- }
78
-
79
- export interface ListControlsRoutes {
80
- list: (pager: object) => Promise<ActionTargetItem[]>;
81
- details?: (item: object) => Promise<ActionTargetItem> | null;
82
- summary?: (filter: object | null) => Promise<object> | null;
83
- filterFieldOptions?: (filter: object | null) => Promise<object> | null;
84
- more?: (pager: object) => Promise<ActionTargetItem[]> | null;
85
- export: (filter: object) => Promise<void>;
86
- }
87
-
88
- export interface ListControlsOptions {
89
- label?: string,
90
- routes: ListControlsRoutes;
91
- urlPattern?: RegExp | null;
92
- filterDefaults?: Record<string, object>;
93
- refreshFilters?: boolean;
94
- }
95
-
96
- export interface ListControlsPagination {
97
- __sort: object[] | null;
98
- sortBy: string | null;
99
- descending: boolean;
100
- page: number;
101
- rowsNumber: number;
102
- rowsPerPage: number;
103
- }
104
-
105
- export interface PagedItems {
106
- data: ActionTargetItem[] | undefined;
107
- meta: {
108
- total: number;
109
- last_page?: number;
110
- } | undefined;
111
- }
112
-
113
- export function useListControls(name: string, options: ListControlsOptions) {
16
+ export function useListControls(name: string, options: ListControlsOptions): ActionController {
114
17
  let isInitialized = false;
18
+ let vueRoute: RouteLocationNormalizedLoaded | null = null;
19
+ let vueRouter: Router | null = null;
115
20
  const PAGE_SETTINGS_KEY = `dx-${name}-pager`;
116
- const pagedItems: Ref<PagedItems | null> = shallowRef(null);
117
- const activeFilter: Ref<ListControlsFilter> = ref({});
21
+ const pagedItems = shallowRef<PagedItems | null>(null);
22
+ const activeFilter = ref<ListControlsFilter>({});
118
23
  const globalFilter = ref({});
119
24
  const showFilters = ref(false);
120
- const selectedRows: ShallowRef<ActionTargetItem[]> = shallowRef([]);
25
+ const selectedRows = shallowRef<ActionTargetItem[]>([]);
121
26
  const isLoadingList = ref(false);
122
27
  const isLoadingSummary = ref(false);
123
- const summary: ShallowRef<object | null> = shallowRef(null);
28
+ const summary = shallowRef<AnyObject | null>(null);
124
29
 
125
30
  // The active ad for viewing / editing
126
- const activeItem: ShallowRef<ActionTargetItem | null> = shallowRef(null);
31
+ const activeItem = shallowRef<ActionTargetItem | null>(null);
127
32
  // Controls the active panel (ie: tab) if rendering a panels drawer or similar
128
- const activePanel: ShallowRef<string> = shallowRef("");
33
+ const activePanel = shallowRef<string>("");
129
34
 
130
- // Filter fields are the field values available for the currently applied filter on Creative Groups
35
+ // Field options are the lists of field values available given the applied filter on the list query. These are used for drop-downs / options in forms, filters, etc.
131
36
  // (ie: all states available under the current filter)
132
- const filterFieldOptions: Ref<AnyObject> = ref({});
37
+ const fieldOptions = ref<AnyObject>({});
133
38
  const isLoadingFilters = ref(false);
134
39
 
135
40
  const filterActiveCount = computed(() => Object.keys(activeFilter.value).filter(key => activeFilter.value[key] !== undefined).length);
@@ -163,7 +68,7 @@ export function useListControls(name: string, options: ListControlsOptions) {
163
68
  watch(selectedRows, loadSummary);
164
69
 
165
70
  if (options.refreshFilters) {
166
- watch(activeFilter, loadFilterFieldOptions);
71
+ watch(activeFilter, loadFieldOptions);
167
72
  }
168
73
 
169
74
  async function loadList() {
@@ -185,11 +90,15 @@ export function useListControls(name: string, options: ListControlsOptions) {
185
90
  isLoadingSummary.value = false;
186
91
  }
187
92
 
93
+ async function loadListAndSummary() {
94
+ await Promise.all([loadList(), loadSummary()]);
95
+ }
96
+
188
97
  /**
189
98
  * Gets the field options for the given field name.
190
99
  */
191
- function getFieldOptions(field: string) {
192
- return filterFieldOptions.value[field] || [];
100
+ function getFieldOptions(field: string): any[] {
101
+ return fieldOptions.value[field] || [];
193
102
  }
194
103
 
195
104
  /**
@@ -197,20 +106,20 @@ export function useListControls(name: string, options: ListControlsOptions) {
197
106
  *
198
107
  * @returns {Promise<void>}
199
108
  */
200
- async function loadFilterFieldOptions() {
201
- if (!options.routes.filterFieldOptions || !isInitialized) return;
109
+ async function loadFieldOptions() {
110
+ if (!options.routes.fieldOptions || !isInitialized) return;
202
111
  isLoadingFilters.value = true;
203
- filterFieldOptions.value = await options.routes.filterFieldOptions(activeFilter.value) || {};
112
+ fieldOptions.value = await options.routes.fieldOptions(activeFilter.value) || {};
204
113
  isLoadingFilters.value = false;
205
114
  }
206
115
 
207
116
  /**
208
117
  * Watches for a filter URL parameter and applies the filter if it is set.
209
118
  */
210
- function applyFilterFromUrl(url: string, filterFields: Ref<FilterGroup[]> | null = null) {
119
+ function applyFilterFromUrl(url: string, filterGroups: Ref<FilterGroup[]> | null = null) {
211
120
  if (options.urlPattern && url.match(options.urlPattern)) {
212
121
  // A flat list of valid filterable field names
213
- const validFilterKeys = filterFields?.value?.map(group => group.fields.map(field => field.name)).flat();
122
+ const validFilterKeys = filterGroups?.value?.map(group => group.fields.map(field => field.name)).flat();
214
123
 
215
124
  const urlFilter = getFilterFromUrl(url, validFilterKeys);
216
125
 
@@ -231,7 +140,6 @@ export function useListControls(name: string, options: ListControlsOptions) {
231
140
  if (Array.isArray(items)) {
232
141
  data = items;
233
142
  meta = { total: items.length };
234
-
235
143
  } else if (items.data) {
236
144
  data = items.data;
237
145
  meta = items.meta;
@@ -243,11 +151,11 @@ export function useListControls(name: string, options: ListControlsOptions) {
243
151
  }
244
152
 
245
153
  // Add a reactive isSaving property to each item (for performance reasons in checking saving state)
246
- data = data.map((item) => {
154
+ data = data.map((item: ActionTargetItem) => {
155
+ item.isSaving = item.isSaving === undefined ? false : item.isSaving;
247
156
  // We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
248
157
  // components, and therefore reactivity is not responding to the new isSaving state
249
- const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
250
- return { ...item, isSaving: oldItem?.isSaving || ref(false) };
158
+ return storeObject(item);
251
159
  });
252
160
 
253
161
  pagedItems.value = { data, meta };
@@ -276,30 +184,17 @@ export function useListControls(name: string, options: ListControlsOptions) {
276
184
  }
277
185
 
278
186
  /**
279
- * Updates a row in the paged items list with the new item data. Uses the item's id to find the row.
280
- *
281
- * @param updatedItem
187
+ * Clears the selected rows in the list.
282
188
  */
283
- function setItemInList(updatedItem: ActionTargetItem) {
284
- if (updatedItem && updatedItem.id) {
285
- const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
286
- setPagedItems({
287
- data,
288
- meta: { total: pagedItems.value?.meta?.total || 0 }
289
- });
290
-
291
- // Update the active item as well if it is set
292
- if (activeItem.value?.id === updatedItem.id) {
293
- activeItem.value = { ...activeItem.value, ...updatedItem };
294
- }
295
- }
189
+ function clearSelectedRows() {
190
+ selectedRows.value = [];
296
191
  }
297
192
 
298
193
  /**
299
194
  * Loads more items into the list.
300
195
  */
301
- async function loadMore(index: number, perPage = undefined) {
302
- if (!options.routes.more) return;
196
+ async function loadMore(index: number, perPage: number | undefined = undefined) {
197
+ if (!options.routes.more) return false;
303
198
 
304
199
  const newItems = await options.routes.more({
305
200
  page: index + 1,
@@ -322,7 +217,7 @@ export function useListControls(name: string, options: ListControlsOptions) {
322
217
  * Refreshes the list, summary, and filter field options.
323
218
  */
324
219
  async function refreshAll() {
325
- return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions(), getActiveItemDetails()]);
220
+ return Promise.all([loadList(), loadSummary(), loadFieldOptions(), getActiveItemDetails()]);
326
221
  }
327
222
 
328
223
  /**
@@ -362,7 +257,7 @@ export function useListControls(name: string, options: ListControlsOptions) {
362
257
  }
363
258
 
364
259
  if (!isLoadingFilters.value) {
365
- loadFilterFieldOptions();
260
+ loadFieldOptions();
366
261
  }
367
262
  }, 1);
368
263
  }
@@ -389,13 +284,12 @@ export function useListControls(name: string, options: ListControlsOptions) {
389
284
 
390
285
  const result = await options.routes.details(activeItem.value);
391
286
 
392
- // Only set the ad details if we are the response for the currently loaded item
393
- // NOTE: race conditions might allow the finished loading item to be different to the currently
394
- // requested item
395
- if (result?.id === activeItem.value?.id) {
396
- const loadedItem = pagedItems.value?.data?.find((i: ActionTargetItem) => i.id === result.id);
397
- activeItem.value = { ...result, isSaving: loadedItem?.isSaving || ref(false) };
287
+ if (!result || !result.__type || !result.id) {
288
+ return console.error("Invalid response from details route: All responses must include a __type and id field. result =", result);
398
289
  }
290
+
291
+ // Reassign the active item to the store object to ensure reactivity
292
+ activeItem.value = storeObject(result);
399
293
  }
400
294
 
401
295
  // Whenever the active item changes, fill the additional item details
@@ -411,16 +305,32 @@ export function useListControls(name: string, options: ListControlsOptions) {
411
305
  /**
412
306
  * Opens the item's form with the given item and tab
413
307
  */
414
- function activatePanel(item: ActionTargetItem | null, panel: string) {
415
- activeItem.value = item;
308
+ function activatePanel(item: ActionTargetItem | null, panel: string = "") {
309
+ // If we're already on the correct item and panel, don't do anything
310
+ if (item?.id === activeItem.value?.id && panel === activePanel.value) return;
311
+
312
+ setActiveItem(item);
416
313
  activePanel.value = panel;
314
+
315
+ // Push vue router change /:id/:panel
316
+ if (vueRoute && vueRouter && item?.id) {
317
+ vueRouter.push({
318
+ name: Array.isArray(vueRoute.name) ? vueRoute.name[0] : vueRoute.name,
319
+ params: { id: item.id, panel },
320
+ replace: true
321
+ });
322
+ }
417
323
  }
418
324
 
419
325
  /**
420
326
  * Sets the currently active item in the list.
421
327
  */
422
328
  function setActiveItem(item: ActionTargetItem | null) {
423
- activeItem.value = item;
329
+ activeItem.value = item ? storeObject(item) : item;
330
+
331
+ if (!item?.id) {
332
+ vueRouter?.push({ name: vueRoute?.name || "home" });
333
+ }
424
334
  }
425
335
 
426
336
  /**
@@ -465,11 +375,11 @@ export function useListControls(name: string, options: ListControlsOptions) {
465
375
  /**
466
376
  * Sets the active filter to the given filter.
467
377
  */
468
- function setActiveFilter(filter: ListControlsFilter) {
469
- activeFilter.value = filter;
378
+ function setActiveFilter(filter?: ListControlsFilter) {
379
+ activeFilter.value = filter || {};
470
380
  }
471
381
 
472
- async function exportList(filter: object) {
382
+ async function exportList(filter?: ListControlsFilter) {
473
383
  return options.routes.export(filter);
474
384
  }
475
385
 
@@ -477,6 +387,33 @@ export function useListControls(name: string, options: ListControlsOptions) {
477
387
  function initialize() {
478
388
  isInitialized = true;
479
389
  loadSettings();
390
+
391
+ // Setup Vue Router handling
392
+ vueRoute = useRoute();
393
+ vueRouter = useRouter();
394
+ /**
395
+ * Watch the id params in the route and set the active item to the item with the given id.
396
+ */
397
+ if (options.routes.details) {
398
+ const { params, meta } = vueRoute;
399
+
400
+ const controlRouteName = vueRoute.name;
401
+ vueRouter.afterEach((to) => {
402
+ if (to.name === controlRouteName) {
403
+ setPanelFromRoute(to.params, to.meta);
404
+ }
405
+ });
406
+
407
+ setPanelFromRoute(params, meta);
408
+ }
409
+ }
410
+
411
+ function setPanelFromRoute(params: RouteParams, meta: AnyObject) {
412
+ const id = Array.isArray(params?.id) ? params.id[0] : params?.id;
413
+ if (id && meta.type) {
414
+ const panel = Array.isArray(params?.panel) ? params.panel[0] : params?.panel;
415
+ activatePanel({ id, __type: "" + meta.type }, panel || activePanel.value || "");
416
+ }
480
417
  }
481
418
 
482
419
  return {
@@ -489,7 +426,6 @@ export function useListControls(name: string, options: ListControlsOptions) {
489
426
  filterActiveCount,
490
427
  showFilters,
491
428
  summary,
492
- filterFieldOptions,
493
429
  selectedRows,
494
430
  isLoadingList,
495
431
  isLoadingFilters,
@@ -501,12 +437,15 @@ export function useListControls(name: string, options: ListControlsOptions) {
501
437
 
502
438
  // Actions
503
439
  initialize,
504
- loadSummary,
505
440
  resetPaging,
506
441
  setPagination,
507
442
  setSelectedRows,
443
+ clearSelectedRows,
508
444
  loadList,
445
+ loadSummary,
446
+ loadListAndSummary,
509
447
  loadMore,
448
+ getActiveItemDetails,
510
449
  refreshAll,
511
450
  exportList,
512
451
  setActiveItem,
@@ -514,7 +453,6 @@ export function useListControls(name: string, options: ListControlsOptions) {
514
453
  activatePanel,
515
454
  setActiveFilter,
516
455
  applyFilterFromUrl,
517
- setItemInList,
518
456
  getFieldOptions
519
457
  };
520
458
  }
@@ -1,7 +1,6 @@
1
- import { ListControlsPagination } from "src/components/ActionTable/listControls";
2
- import { TableColumn } from "src/components/ActionTable/tableColumns";
3
1
  import { onMounted, Ref } from "vue";
4
- import { AnyObject, getUrlParam } from "../../helpers";
2
+ import { getUrlParam } from "../../helpers";
3
+ import { AnyObject, ListControlsPagination, TableColumn } from "../../types";
5
4
 
6
5
  export function registerStickyScrolling(tableRef: Ref) {
7
6
  onMounted(() => {
@@ -1,82 +1,58 @@
1
1
  import { computed, ref, watch } from "vue";
2
2
  import { getItem, setItem } from "../../helpers";
3
-
4
- export interface TableColumn {
5
- actionMenu?: object,
6
- align?: string,
7
- category?: string,
8
- class?: string | object,
9
- field: string,
10
- format?: Function,
11
- innerClass?: string | object,
12
- style?: string | object,
13
- headerStyle?: string | object,
14
- isSavingRow?: boolean | Function,
15
- label: string,
16
- maxWidth?: number,
17
- minWidth?: number,
18
- name: string,
19
- onClick?: Function,
20
- required?: boolean,
21
- resizeable?: boolean,
22
- sortable?: boolean,
23
- sortBy?: string,
24
- sortByExpression?: string,
25
- titleColumns?: Function,
26
- vnode?: Function,
27
- }
3
+ import { TableColumn } from "../../types";
28
4
 
29
5
  export function useTableColumns(name: string, columns: TableColumn[]) {
30
- const COLUMN_ORDER_KEY = `${name}-column-order`;
31
- const VISIBLE_COLUMNS_KEY = `${name}-visible-columns`;
32
- const TITLE_COLUMNS_KEY = `${name}-title-columns`;
33
-
34
- // The list that defines the order the columns should appear in
35
- const columnOrder = ref(getItem(COLUMN_ORDER_KEY) || []);
36
-
37
- // Manages visible columns on the table
38
- const hiddenColumnNames = ref(getItem(VISIBLE_COLUMNS_KEY, []));
39
-
40
- // Title columns will have their name appear on the first column of the table as part of the records' title
41
- const titleColumnNames = ref(getItem(TITLE_COLUMNS_KEY, []));
42
-
43
- // Columns that should be locked to the left side of the table
44
- const lockedColumns = computed(() => orderedColumns.value.slice(0, 1));
45
-
46
- // The resolved list of columns in the order they should appear in
47
- const orderedColumns = computed(() => [...columns].sort((a, b) => {
48
- const aIndex = columnOrder.value.indexOf(a.name);
49
- const bIndex = columnOrder.value.indexOf(b.name);
50
- return aIndex === -1 ? 1 : bIndex === -1 ? -1 : aIndex - bIndex;
51
- }));
52
-
53
- // The ordered list of columns. The ordering of this list is editable and will be stored in localStorage
54
- const sortableColumns = computed({
55
- get() {
56
- return orderedColumns.value.slice(1);
57
- },
58
- set(newColumns) {
59
- columnOrder.value = [...lockedColumns.value.map(c => c.name), ...newColumns.map(c => c.name)];
60
- setItem(COLUMN_ORDER_KEY, columnOrder.value);
61
- }
62
- });
63
-
64
- // The list of columns that are visible. To edit the visible columns, edit the hiddenColumnNames list
65
- const visibleColumns = computed(() => orderedColumns.value.filter(c => !hiddenColumnNames.value.includes(c.name)));
66
-
67
- // The list of columns that should be included in the title of a row
68
- const orderedTitleColumns = computed(() => orderedColumns.value.filter(c => titleColumnNames.value.includes(c.name)));
69
-
70
- // Save changes to the list of hidden columns in localStorage
71
- watch(() => hiddenColumnNames.value, () => setItem(VISIBLE_COLUMNS_KEY, hiddenColumnNames.value));
72
- watch(() => titleColumnNames.value, () => setItem(TITLE_COLUMNS_KEY, titleColumnNames.value));
73
-
74
- return {
75
- sortableColumns,
76
- lockedColumns,
77
- visibleColumns,
78
- hiddenColumnNames,
79
- titleColumnNames,
80
- orderedTitleColumns
81
- };
6
+ const COLUMN_ORDER_KEY = `${name}-column-order`;
7
+ const VISIBLE_COLUMNS_KEY = `${name}-visible-columns`;
8
+ const TITLE_COLUMNS_KEY = `${name}-title-columns`;
9
+
10
+ // The list that defines the order the columns should appear in
11
+ const columnOrder = ref(getItem(COLUMN_ORDER_KEY) || []);
12
+
13
+ // Manages visible columns on the table
14
+ const hiddenColumnNames = ref(getItem(VISIBLE_COLUMNS_KEY, []));
15
+
16
+ // Title columns will have their name appear on the first column of the table as part of the records' title
17
+ const titleColumnNames = ref(getItem(TITLE_COLUMNS_KEY, []));
18
+
19
+ // Columns that should be locked to the left side of the table
20
+ const lockedColumns = computed(() => orderedColumns.value.slice(0, 1));
21
+
22
+ // The resolved list of columns in the order they should appear in
23
+ const orderedColumns = computed(() => [...columns].sort((a, b) => {
24
+ const aIndex = columnOrder.value.indexOf(a.name);
25
+ const bIndex = columnOrder.value.indexOf(b.name);
26
+ return aIndex === -1 ? 1 : bIndex === -1 ? -1 : aIndex - bIndex;
27
+ }));
28
+
29
+ // The ordered list of columns. The ordering of this list is editable and will be stored in localStorage
30
+ const sortableColumns = computed({
31
+ get() {
32
+ return orderedColumns.value.slice(1);
33
+ },
34
+ set(newColumns) {
35
+ columnOrder.value = [...lockedColumns.value.map(c => c.name), ...newColumns.map(c => c.name)];
36
+ setItem(COLUMN_ORDER_KEY, columnOrder.value);
37
+ }
38
+ });
39
+
40
+ // The list of columns that are visible. To edit the visible columns, edit the hiddenColumnNames list
41
+ const visibleColumns = computed(() => orderedColumns.value.filter(c => !hiddenColumnNames.value.includes(c.name)));
42
+
43
+ // The list of columns that should be included in the title of a row
44
+ const orderedTitleColumns = computed(() => orderedColumns.value.filter(c => titleColumnNames.value.includes(c.name)));
45
+
46
+ // Save changes to the list of hidden columns in localStorage
47
+ watch(() => hiddenColumnNames.value, () => setItem(VISIBLE_COLUMNS_KEY, hiddenColumnNames.value));
48
+ watch(() => titleColumnNames.value, () => setItem(TITLE_COLUMNS_KEY, titleColumnNames.value));
49
+
50
+ return {
51
+ sortableColumns,
52
+ lockedColumns,
53
+ visibleColumns,
54
+ hiddenColumnNames,
55
+ titleColumnNames,
56
+ orderedTitleColumns
57
+ };
82
58
  }
@@ -2,7 +2,7 @@
2
2
  <div class="flex space-x-2">
3
3
  <template v-if="type === 'SINGLE_FILE'">
4
4
  <FilePreview
5
- :image="value"
5
+ :file="value"
6
6
  class="w-24"
7
7
  />
8
8
  </template>
@@ -10,7 +10,7 @@
10
10
  <FilePreview
11
11
  v-for="file in value"
12
12
  :key="'file-' + file.id"
13
- :image="file"
13
+ :file="file"
14
14
  class="w-24 mb-2"
15
15
  />
16
16
  </template>
@@ -27,34 +27,34 @@ import { fCurrency, fDate, fLocalizedDateTime, fNumber } from "../../helpers";
27
27
  import { FilePreview } from "../Utility";
28
28
 
29
29
  const props = defineProps({
30
- type: {
31
- type: String,
32
- required: true
33
- },
34
- value: {
35
- type: [Number, String, Array, Object, Boolean],
36
- default: null
37
- }
30
+ type: {
31
+ type: String,
32
+ required: true
33
+ },
34
+ value: {
35
+ type: [Number, String, Array, Object, Boolean],
36
+ default: null
37
+ }
38
38
  });
39
39
 
40
40
  function format(value) {
41
- if (value === null || value === "" || value === undefined) {
42
- return "";
43
- }
41
+ if (value === null || value === "" || value === undefined) {
42
+ return "";
43
+ }
44
44
 
45
- switch (props.type) {
46
- case "NUMBER":
47
- return fNumber(value);
48
- case "CURRENCY":
49
- return fCurrency(value);
50
- case "DATE":
51
- return fDate(value);
52
- case "DATETIME":
53
- return fLocalizedDateTime(value);
54
- case "BOOLEAN":
55
- return value ? "Yes" : "No";
56
- }
45
+ switch (props.type) {
46
+ case "NUMBER":
47
+ return fNumber(value);
48
+ case "CURRENCY":
49
+ return fCurrency(value);
50
+ case "DATE":
51
+ return fDate(value);
52
+ case "DATETIME":
53
+ return fLocalizedDateTime(value);
54
+ case "BOOLEAN":
55
+ return value ? "Yes" : "No";
56
+ }
57
57
 
58
- return value;
58
+ return value;
59
59
  }
60
60
  </script>