quasar-ui-danx 0.0.24 → 0.0.26
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/package.json +1 -1
- package/src/components/ActionTable/ActionMenu.vue +97 -0
- package/src/components/ActionTable/ActionTable.vue +25 -12
- package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +2 -2
- package/src/components/ActionTable/Filters/FilterGroupList.vue +5 -5
- package/src/components/ActionTable/index.ts +1 -0
- package/src/components/ActionTable/listActions.ts +8 -12
- package/src/components/Utility/Popovers/PopoverMenu.vue +10 -5
package/package.json
CHANGED
@@ -0,0 +1,97 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<PopoverMenu
|
4
|
+
class="px-4 h-full flex"
|
5
|
+
:items="items"
|
6
|
+
@action-item="onAction"
|
7
|
+
/>
|
8
|
+
<Component
|
9
|
+
v-if="confirmDialog"
|
10
|
+
:is="confirmDialog.is"
|
11
|
+
v-bind="confirmDialog.props"
|
12
|
+
:is-saving="isSaving"
|
13
|
+
@close="onCancel"
|
14
|
+
@confirm="onConfirmAction"
|
15
|
+
/>
|
16
|
+
</div>
|
17
|
+
</template>
|
18
|
+
<script setup>
|
19
|
+
import { ref, shallowRef } from 'vue';
|
20
|
+
import { FlashMessages } from '../../helpers';
|
21
|
+
import { PopoverMenu } from '../Utility';
|
22
|
+
|
23
|
+
const emit = defineEmits(['action']);
|
24
|
+
const props = defineProps({
|
25
|
+
items: {
|
26
|
+
type: Array,
|
27
|
+
required: true
|
28
|
+
},
|
29
|
+
rows: {
|
30
|
+
type: Array,
|
31
|
+
required: true
|
32
|
+
}
|
33
|
+
});
|
34
|
+
|
35
|
+
|
36
|
+
const activeAction = shallowRef(null);
|
37
|
+
const confirmDialog = shallowRef(null);
|
38
|
+
const isSaving = ref(false);
|
39
|
+
|
40
|
+
function onAction(item) {
|
41
|
+
emit('action', item);
|
42
|
+
|
43
|
+
activeAction.value = item;
|
44
|
+
|
45
|
+
if (item.confirmDialog) {
|
46
|
+
confirmDialog.value = typeof item.confirmDialog === 'function' ? item.confirmDialog(props.rows) : item.confirmDialog;
|
47
|
+
} else {
|
48
|
+
console.log('handle default action');
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
function onCancel() {
|
53
|
+
activeAction.value = null;
|
54
|
+
confirmDialog.value = null;
|
55
|
+
}
|
56
|
+
|
57
|
+
async function onConfirmAction(input) {
|
58
|
+
if (!activeAction.value.onAction) {
|
59
|
+
throw new Error('No onAction handler found for the selected action:' + activeAction.value.action);
|
60
|
+
}
|
61
|
+
|
62
|
+
isSaving.value = true;
|
63
|
+
const result = await activeAction.value.onAction(input, props.rows);
|
64
|
+
isSaving.value = false;
|
65
|
+
|
66
|
+
if (!result.success) {
|
67
|
+
const errors = [];
|
68
|
+
if (result.errors) {
|
69
|
+
errors.push(...result.errors);
|
70
|
+
} else if (result.error) {
|
71
|
+
errors.push(result.error.message);
|
72
|
+
} else {
|
73
|
+
errors.push('An unknown error occurred. Please try again later.');
|
74
|
+
}
|
75
|
+
|
76
|
+
FlashMessages.combine('error', errors);
|
77
|
+
|
78
|
+
if (activeAction.value.onError) {
|
79
|
+
await activeAction.value.onError(result, input);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
FlashMessages.success(`The update was successful`);
|
84
|
+
|
85
|
+
if (activeAction.value.onSuccess) {
|
86
|
+
await activeAction.value.onSuccess(result, input);
|
87
|
+
}
|
88
|
+
|
89
|
+
if (activeAction.value.onFinish) {
|
90
|
+
await activeAction.value.onFinish();
|
91
|
+
}
|
92
|
+
|
93
|
+
confirmDialog.value = null;
|
94
|
+
activeAction.value = null;
|
95
|
+
}
|
96
|
+
|
97
|
+
</script>
|
@@ -12,7 +12,7 @@
|
|
12
12
|
color="blue-base"
|
13
13
|
@update:selected="$emit('update:selected-rows', $event)"
|
14
14
|
@update:pagination="() => {}"
|
15
|
-
@request="$emit('update:quasar-pagination', $event.pagination)"
|
15
|
+
@request="$emit('update:quasar-pagination', {...$event.pagination, __sort: mapSortBy($event.pagination, columns)})"
|
16
16
|
>
|
17
17
|
<template #no-data>
|
18
18
|
<slot name="empty">
|
@@ -51,24 +51,30 @@
|
|
51
51
|
<q-td :key="rowProps.key" :props="rowProps">
|
52
52
|
<component
|
53
53
|
:is="rowProps.col.onClick ? 'a' : 'div'"
|
54
|
+
class="flex items-center flex-nowrap"
|
54
55
|
@click="() => rowProps.col.onClick && rowProps.col.onClick(rowProps.row)"
|
55
56
|
>
|
56
|
-
<
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
<template v-else-if="rowProps.col.fieldList">
|
57
|
+
<RenderComponent
|
58
|
+
v-if="rowProps.col.component"
|
59
|
+
:row-props="rowProps"
|
60
|
+
@action="$emit('action', $event)"
|
61
|
+
/>
|
62
|
+
<div v-else-if="rowProps.col.fieldList">
|
63
63
|
<div v-for="field in rowProps.col.fieldList" :key="field">
|
64
64
|
{{ rowProps.row[field] }}
|
65
65
|
</div>
|
66
|
-
</
|
67
|
-
<
|
66
|
+
</div>
|
67
|
+
<div v-else>
|
68
68
|
<slot v-bind="{name: rowProps.col.name, row: rowProps.row, value: rowProps.value}">
|
69
69
|
{{ rowProps.value }}
|
70
70
|
</slot>
|
71
|
-
</
|
71
|
+
</div>
|
72
|
+
<ActionMenu
|
73
|
+
v-if="rowProps.col.actions" class="ml-2"
|
74
|
+
:items="rowProps.col.actions"
|
75
|
+
:rows="[rowProps.row]"
|
76
|
+
@action="(action) => $emit('action', {action: action, row: rowProps.row})"
|
77
|
+
/>
|
72
78
|
</component>
|
73
79
|
</q-td>
|
74
80
|
</template>
|
@@ -79,7 +85,14 @@
|
|
79
85
|
import { ref } from 'vue';
|
80
86
|
import { DragHandleIcon as RowResizeIcon } from '../../svg';
|
81
87
|
import { HandleDraggable } from '../DragAndDrop';
|
82
|
-
import {
|
88
|
+
import {
|
89
|
+
ActionMenu,
|
90
|
+
EmptyTableState,
|
91
|
+
mapSortBy,
|
92
|
+
registerStickyScrolling,
|
93
|
+
RenderComponent,
|
94
|
+
TableSummaryRow
|
95
|
+
} from './index';
|
83
96
|
|
84
97
|
defineEmits(['action', 'filter', 'update:quasar-pagination', 'update:selected-rows']);
|
85
98
|
defineProps({
|
@@ -9,7 +9,7 @@
|
|
9
9
|
>
|
10
10
|
<FilterGroupList
|
11
11
|
:filter="filter"
|
12
|
-
:filter-
|
12
|
+
:filter-fields="filterFields"
|
13
13
|
@update:filter="$emit('update:filter', $event)"
|
14
14
|
/>
|
15
15
|
</CollapsableSidebar>
|
@@ -29,7 +29,7 @@ defineProps({
|
|
29
29
|
type: Object,
|
30
30
|
default: null
|
31
31
|
},
|
32
|
-
|
32
|
+
filterFields: {
|
33
33
|
type: Array,
|
34
34
|
default: () => []
|
35
35
|
}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<q-list>
|
3
3
|
<div class="px-4 py-2 max-w-full">
|
4
4
|
<template
|
5
|
-
v-for="(group, index) in
|
5
|
+
v-for="(group, index) in filterFields"
|
6
6
|
:key="'group-' + group.name"
|
7
7
|
>
|
8
8
|
<template v-if="group.flat">
|
@@ -34,12 +34,12 @@
|
|
34
34
|
</FilterGroupItem>
|
35
35
|
|
36
36
|
<q-separator
|
37
|
-
v-if="index < (
|
37
|
+
v-if="index < (filterFields.length - 1)"
|
38
38
|
class="my-2"
|
39
39
|
/>
|
40
40
|
</template>
|
41
41
|
</div>
|
42
|
-
|
42
|
+
</q-list>
|
43
43
|
</template>
|
44
44
|
<script setup>
|
45
45
|
import { computed } from 'vue';
|
@@ -48,7 +48,7 @@ import FilterGroupItem from './FilterGroupItem';
|
|
48
48
|
|
49
49
|
const emit = defineEmits(['update:filter']);
|
50
50
|
const props = defineProps({
|
51
|
-
|
51
|
+
filterFields: {
|
52
52
|
type: Array,
|
53
53
|
required: true
|
54
54
|
},
|
@@ -61,7 +61,7 @@ const props = defineProps({
|
|
61
61
|
|
62
62
|
const activeCountByGroup = computed(() => {
|
63
63
|
const activeCountByGroup = {};
|
64
|
-
for (const group of props.
|
64
|
+
for (const group of props.filterFields) {
|
65
65
|
activeCountByGroup[group.name] = group.fields.filter(field => props.filter[field.name] !== undefined).length;
|
66
66
|
}
|
67
67
|
return activeCountByGroup;
|
@@ -3,6 +3,7 @@ export * from "./Form";
|
|
3
3
|
export * from "./listActions";
|
4
4
|
export * from "./listHelpers";
|
5
5
|
export * from "./tableColumns";
|
6
|
+
export { default as ActionMenu } from "./ActionMenu.vue";
|
6
7
|
export { default as ActionTable } from "./ActionTable.vue";
|
7
8
|
export { default as BatchActionMenu } from "./BatchActionMenu.vue";
|
8
9
|
export { default as EmptyTableState } from "./EmptyTableState.vue";
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { computed, ref, watch } from "vue";
|
2
2
|
import { getItem, setItem } from "../../helpers";
|
3
|
-
import { getFilterFromUrl,
|
3
|
+
import { getFilterFromUrl, waitForRef } from "./listHelpers";
|
4
4
|
|
5
5
|
export function useListActions(name, {
|
6
6
|
listRoute,
|
@@ -10,7 +10,6 @@ export function useListActions(name, {
|
|
10
10
|
applyActionRoute = null,
|
11
11
|
applyBatchActionRoute = null,
|
12
12
|
itemDetailsRoute = null,
|
13
|
-
columns = null,
|
14
13
|
filterGroups = null,
|
15
14
|
refreshFilters = false,
|
16
15
|
urlPattern = null,
|
@@ -30,6 +29,7 @@ export function useListActions(name, {
|
|
30
29
|
const filterActiveCount = computed(() => Object.keys(filter.value).filter(key => filter.value[key] !== undefined).length);
|
31
30
|
|
32
31
|
const PAGING_DEFAULT = {
|
32
|
+
__sort: null,
|
33
33
|
sortBy: null,
|
34
34
|
descending: false,
|
35
35
|
page: 1,
|
@@ -42,7 +42,7 @@ export function useListActions(name, {
|
|
42
42
|
perPage: quasarPagination.value.rowsPerPage,
|
43
43
|
page: quasarPagination.value.page,
|
44
44
|
filter: { ...filter.value, ...globalFilter.value },
|
45
|
-
sort:
|
45
|
+
sort: quasarPagination.value.__sort || undefined
|
46
46
|
}));
|
47
47
|
|
48
48
|
// When any part of the filter changes, get the new list of creatives
|
@@ -95,15 +95,15 @@ export function useListActions(name, {
|
|
95
95
|
isLoadingFilters.value = false;
|
96
96
|
}
|
97
97
|
|
98
|
-
// A flat list of valid filterable field names
|
99
|
-
const validFilterKeys = computed(() => filterGroups?.value?.map(group => group.fields.map(field => field.name)).flat());
|
100
|
-
|
101
98
|
/**
|
102
99
|
* Watches for a filter URL parameter and applies the filter if it is set.
|
103
100
|
*/
|
104
|
-
function applyFilterFromUrl(url) {
|
101
|
+
function applyFilterFromUrl(url, filterGroups = null) {
|
105
102
|
if (url.match(urlPattern)) {
|
106
|
-
|
103
|
+
// A flat list of valid filterable field names
|
104
|
+
const validFilterKeys = filterGroups?.value?.map(group => group.fields.map(field => field.name)).flat();
|
105
|
+
|
106
|
+
const urlFilter = getFilterFromUrl(url, validFilterKeys);
|
107
107
|
|
108
108
|
if (Object.keys(urlFilter).length > 0) {
|
109
109
|
filter.value = urlFilter;
|
@@ -187,9 +187,6 @@ export function useListActions(name, {
|
|
187
187
|
filter.value = { ...filterDefaults, ...filter.value };
|
188
188
|
}
|
189
189
|
|
190
|
-
// Load the URL filters if they are set
|
191
|
-
applyFilterFromUrl(window.location.href);
|
192
|
-
|
193
190
|
setTimeout(() => {
|
194
191
|
if (!isLoadingList.value) {
|
195
192
|
loadList();
|
@@ -355,7 +352,6 @@ export function useListActions(name, {
|
|
355
352
|
isApplyingBatchAction,
|
356
353
|
activeItem,
|
357
354
|
formTab,
|
358
|
-
columns,
|
359
355
|
filterGroups,
|
360
356
|
|
361
357
|
// Actions
|
@@ -37,19 +37,19 @@
|
|
37
37
|
:key="item.action"
|
38
38
|
clickable
|
39
39
|
:class="item.class"
|
40
|
-
@click="
|
40
|
+
@click="onAction(item)"
|
41
41
|
>
|
42
42
|
{{ item.label }}
|
43
|
-
|
43
|
+
</q-item>
|
44
44
|
</template>
|
45
|
-
|
46
|
-
|
45
|
+
</q-list>
|
46
|
+
</q-menu>
|
47
47
|
</a>
|
48
48
|
</template>
|
49
49
|
<script setup>
|
50
50
|
import { DotsVerticalIcon as MenuIcon } from '@heroicons/vue/outline';
|
51
51
|
|
52
|
-
defineEmits(['action']);
|
52
|
+
const emit = defineEmits(['action', 'action-item']);
|
53
53
|
defineProps({
|
54
54
|
items: {
|
55
55
|
type: Array,
|
@@ -61,4 +61,9 @@ defineProps({
|
|
61
61
|
disabled: Boolean,
|
62
62
|
loading: Boolean
|
63
63
|
});
|
64
|
+
|
65
|
+
function onAction(item) {
|
66
|
+
emit('action', item.action);
|
67
|
+
emit('action-item', item);
|
68
|
+
}
|
64
69
|
</script>
|