quasar-ui-danx 0.2.31 → 0.3.0
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/dist/danx.es.js +6560 -6313
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +5 -5
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionTable.vue +9 -45
- package/src/components/ActionTable/ActionTableColumn.vue +5 -2
- package/src/components/ActionTable/ActionTableHeaderColumn.vue +83 -0
- package/src/components/ActionTable/Filters/FilterListToggle.vue +1 -2
- package/src/components/ActionTable/Form/Fields/BooleanField.vue +9 -2
- package/src/components/ActionTable/Form/RenderedForm.vue +146 -10
- package/src/components/ActionTable/index.ts +1 -0
- package/src/components/ActionTable/listControls.ts +68 -38
- package/src/components/ActionTable/tableColumns.ts +3 -3
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
- package/src/components/Utility/Tools/RenderVnode.vue +16 -2
- package/src/helpers/actions.ts +34 -31
- package/src/helpers/array.ts +11 -16
- package/src/helpers/utils.ts +20 -25
- package/src/styles/quasar-reset.scss +4 -0
@@ -34,22 +34,12 @@
|
|
34
34
|
/>
|
35
35
|
</template>
|
36
36
|
<template #header-cell="rowProps">
|
37
|
-
<
|
38
|
-
|
39
|
-
:props="rowProps"
|
40
|
-
:
|
41
|
-
:
|
42
|
-
|
43
|
-
{{ rowProps.col.label }}
|
44
|
-
<HandleDraggable
|
45
|
-
v-if="rowProps.col.resizeable"
|
46
|
-
:drop-zone="`resize-column-` + rowProps.col.name"
|
47
|
-
:class="cls['resize-handle']"
|
48
|
-
@resize="onResizeColumn(rowProps.col, $event)"
|
49
|
-
>
|
50
|
-
<RowResizeIcon class="w-4 text-gray-600" />
|
51
|
-
</HandleDraggable>
|
52
|
-
</QTh>
|
37
|
+
<ActionTableHeaderColumn
|
38
|
+
v-model="columnSettings"
|
39
|
+
:row-props="rowProps"
|
40
|
+
:name="name"
|
41
|
+
@update:model-value="onUpdateColumnSettings"
|
42
|
+
/>
|
53
43
|
</template>
|
54
44
|
<template #body-cell="rowProps">
|
55
45
|
<ActionTableColumn
|
@@ -65,13 +55,12 @@
|
|
65
55
|
</template>
|
66
56
|
|
67
57
|
<script setup>
|
68
|
-
import { QTable
|
58
|
+
import { QTable } from "quasar";
|
69
59
|
import { ref } from "vue";
|
70
60
|
import { getItem, setItem } from "../../helpers";
|
71
|
-
import { DragHandleIcon as RowResizeIcon } from "../../svg";
|
72
|
-
import { HandleDraggable } from "../DragAndDrop";
|
73
61
|
import { ActionVnode } from "../Utility";
|
74
62
|
import ActionTableColumn from "./ActionTableColumn.vue";
|
63
|
+
import ActionTableHeaderColumn from "./ActionTableHeaderColumn";
|
75
64
|
import EmptyTableState from "./EmptyTableState.vue";
|
76
65
|
import { mapSortBy, registerStickyScrolling } from "./listHelpers";
|
77
66
|
import TableSummaryRow from "./TableSummaryRow.vue";
|
@@ -118,32 +107,7 @@ registerStickyScrolling(actionTable);
|
|
118
107
|
|
119
108
|
const COLUMN_SETTINGS_KEY = `column-settings-${props.name}`;
|
120
109
|
const columnSettings = ref(getItem(COLUMN_SETTINGS_KEY) || {});
|
121
|
-
function
|
122
|
-
columnSettings.value = {
|
123
|
-
...columnSettings.value,
|
124
|
-
[column.name]: {
|
125
|
-
width: Math.max(Math.min(val.distance + val.startDropZoneSize, column.maxWidth || 500), column.minWidth || 80)
|
126
|
-
}
|
127
|
-
};
|
110
|
+
function onUpdateColumnSettings() {
|
128
111
|
setItem(COLUMN_SETTINGS_KEY, columnSettings.value);
|
129
112
|
}
|
130
113
|
</script>
|
131
|
-
|
132
|
-
<style lang="scss" module="cls">
|
133
|
-
.handle-drop-zone {
|
134
|
-
.resize-handle {
|
135
|
-
position: absolute;
|
136
|
-
top: 0;
|
137
|
-
right: -.45em;
|
138
|
-
width: .9em;
|
139
|
-
opacity: 0;
|
140
|
-
transition: all .3s;
|
141
|
-
}
|
142
|
-
|
143
|
-
&:hover {
|
144
|
-
.resize-handle {
|
145
|
-
opacity: 1;
|
146
|
-
}
|
147
|
-
}
|
148
|
-
}
|
149
|
-
</style>
|
@@ -52,11 +52,14 @@ const props = defineProps({
|
|
52
52
|
const row = computed(() => props.rowProps.row);
|
53
53
|
const column = computed(() => props.rowProps.col);
|
54
54
|
const value = computed(() => props.rowProps.value);
|
55
|
-
const isSaving = computed(() =>
|
55
|
+
const isSaving = computed(() => row.value.isSaving?.value);
|
56
56
|
|
57
57
|
const columnStyle = computed(() => {
|
58
58
|
const width = props.settings?.width || column.value.width;
|
59
|
-
return
|
59
|
+
return {
|
60
|
+
width: width ? `${width}px` : undefined,
|
61
|
+
minWidth: column.value.minWidth ? `${column.value.minWidth}px` : undefined
|
62
|
+
};
|
60
63
|
});
|
61
64
|
|
62
65
|
const columnClass = computed(() => ({
|
@@ -0,0 +1,83 @@
|
|
1
|
+
<template>
|
2
|
+
<QTh
|
3
|
+
:key="rowProps.key"
|
4
|
+
:props="rowProps"
|
5
|
+
:data-drop-zone="isResizeable && `resize-column-` + column.name"
|
6
|
+
:class="isResizeable && cls['handle-drop-zone']"
|
7
|
+
:style="columnStyle"
|
8
|
+
>
|
9
|
+
{{ column.label }}
|
10
|
+
<HandleDraggable
|
11
|
+
v-if="isResizeable"
|
12
|
+
:drop-zone="`resize-column-` + column.name"
|
13
|
+
:class="cls['resize-handle']"
|
14
|
+
@resize="onResizeColumn"
|
15
|
+
>
|
16
|
+
<RowResizeIcon class="w-4 text-gray-600" />
|
17
|
+
</HandleDraggable>
|
18
|
+
</QTh>
|
19
|
+
</template>
|
20
|
+
<script setup>
|
21
|
+
import { QTh } from "quasar";
|
22
|
+
import { computed } from "vue";
|
23
|
+
import { DragHandleIcon as RowResizeIcon } from "../../svg";
|
24
|
+
import { HandleDraggable } from "../DragAndDrop";
|
25
|
+
|
26
|
+
const emit = defineEmits(["update:model-value"]);
|
27
|
+
const props = defineProps({
|
28
|
+
modelValue: {
|
29
|
+
type: Object,
|
30
|
+
required: true
|
31
|
+
},
|
32
|
+
name: {
|
33
|
+
type: String,
|
34
|
+
required: true
|
35
|
+
},
|
36
|
+
rowProps: {
|
37
|
+
type: Object,
|
38
|
+
required: true
|
39
|
+
}
|
40
|
+
});
|
41
|
+
|
42
|
+
const column = computed(() => props.rowProps.col);
|
43
|
+
const isResizeable = computed(() => column.value.resizeable);
|
44
|
+
|
45
|
+
const columnStyle = computed(() => {
|
46
|
+
const width = props.settings?.width || column.value.width;
|
47
|
+
return {
|
48
|
+
width: width ? `${width}px` : undefined,
|
49
|
+
minWidth: column.value.minWidth ? `${column.value.minWidth}px` : undefined,
|
50
|
+
...(column.value.headerStyle || {})
|
51
|
+
};
|
52
|
+
});
|
53
|
+
|
54
|
+
|
55
|
+
function onResizeColumn(val) {
|
56
|
+
const settings = {
|
57
|
+
...props.modelValue,
|
58
|
+
[column.value.name]: {
|
59
|
+
width: Math.max(Math.min(val.distance + val.startDropZoneSize, column.value.maxWidth || 500), column.value.minWidth || 80)
|
60
|
+
}
|
61
|
+
};
|
62
|
+
emit("update:model-value", settings);
|
63
|
+
}
|
64
|
+
</script>
|
65
|
+
|
66
|
+
<style lang="scss" module="cls">
|
67
|
+
.handle-drop-zone {
|
68
|
+
.resize-handle {
|
69
|
+
position: absolute;
|
70
|
+
top: 0;
|
71
|
+
right: -.45em;
|
72
|
+
width: .9em;
|
73
|
+
opacity: 0;
|
74
|
+
transition: all .3s;
|
75
|
+
}
|
76
|
+
|
77
|
+
&:hover {
|
78
|
+
.resize-handle {
|
79
|
+
opacity: 1;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
</style>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<div class="flex items-center transition-all" :class="{'w-72': showFilters, 'w-[6.5rem]': !showFilters}">
|
3
3
|
<div class="flex-grow">
|
4
4
|
<QBtn
|
5
|
-
class="
|
5
|
+
class="btn-blue-highlight border-blue-700"
|
6
6
|
:class="{'highlighted': showFilters}"
|
7
7
|
@click="$emit('update:show-filters', !showFilters)"
|
8
8
|
>
|
@@ -38,7 +38,6 @@ const props = defineProps({
|
|
38
38
|
const activeCount = computed(() => Object.keys(props.filter).filter(key => props.filter[key] !== undefined).length);
|
39
39
|
</script>
|
40
40
|
<style lang="scss" scoped>
|
41
|
-
|
42
41
|
.btn-blue-highlight {
|
43
42
|
@apply rounded-lg border border-solid;
|
44
43
|
|
@@ -2,12 +2,13 @@
|
|
2
2
|
<QToggle
|
3
3
|
:data-testid="'boolean-field-' + field.id"
|
4
4
|
:model-value="modelValue"
|
5
|
+
:disable="disable || readonly"
|
5
6
|
:toggle-indeterminate="toggleIndeterminate"
|
6
7
|
:indeterminate-value="undefined"
|
7
8
|
@update:model-value="$emit('update:model-value', $event)"
|
8
9
|
>
|
9
10
|
<FieldLabel
|
10
|
-
:field="field"
|
11
|
+
:field="{...field, label}"
|
11
12
|
:show-name="showName"
|
12
13
|
:class="labelClass"
|
13
14
|
/>
|
@@ -19,6 +20,10 @@ import FieldLabel from "./FieldLabel";
|
|
19
20
|
|
20
21
|
defineEmits(["update:model-value"]);
|
21
22
|
defineProps({
|
23
|
+
label: {
|
24
|
+
type: String,
|
25
|
+
default: null
|
26
|
+
},
|
22
27
|
modelValue: {
|
23
28
|
type: [Boolean],
|
24
29
|
default: undefined
|
@@ -32,6 +37,8 @@ defineProps({
|
|
32
37
|
default: "text-sm"
|
33
38
|
},
|
34
39
|
showName: Boolean,
|
35
|
-
toggleIndeterminate: Boolean
|
40
|
+
toggleIndeterminate: Boolean,
|
41
|
+
disable: Boolean,
|
42
|
+
readonly: Boolean
|
36
43
|
});
|
37
44
|
</script>
|
@@ -1,5 +1,46 @@
|
|
1
1
|
<template>
|
2
2
|
<div class="rendered-form">
|
3
|
+
<div v-if="form.variations > 1" class="mb-4">
|
4
|
+
<QTabs v-model="currentVariation" class="text-xs">
|
5
|
+
<QTab
|
6
|
+
v-for="(name, index) in variationNames"
|
7
|
+
:key="name"
|
8
|
+
:name="name"
|
9
|
+
class="p-0"
|
10
|
+
>
|
11
|
+
<div class="flex flex-nowrap items-center text-sm">
|
12
|
+
<div>{{ name }}</div>
|
13
|
+
<template v-if="!disable && !readonly">
|
14
|
+
<a
|
15
|
+
@click="() => (variationToEdit = name) && (newVariationName = name)"
|
16
|
+
class="ml-1 p-1 hover:opacity-100 opacity-20 hover:bg-blue-200 rounded"
|
17
|
+
>
|
18
|
+
<EditIcon class="w-3 text-blue-900" />
|
19
|
+
</a>
|
20
|
+
<a
|
21
|
+
v-if="index > 0"
|
22
|
+
@click="variationToDelete = name"
|
23
|
+
class="ml-1 p-1 hover:opacity-100 opacity-20 hover:bg-red-200 rounded"
|
24
|
+
>
|
25
|
+
<RemoveIcon class="w-3 text-red-900" />
|
26
|
+
</a>
|
27
|
+
</template>
|
28
|
+
</div>
|
29
|
+
</QTab>
|
30
|
+
<QTab
|
31
|
+
v-if="canAddVariation"
|
32
|
+
name="add"
|
33
|
+
key="add-new-variation"
|
34
|
+
@click="onAddVariation"
|
35
|
+
class="bg-blue-600 rounded-t-lg !text-white"
|
36
|
+
>
|
37
|
+
<template v-if="saving">
|
38
|
+
<QSpinnerBall class="w-4" />
|
39
|
+
</template>
|
40
|
+
<template v-else>+ Add Variation</template>
|
41
|
+
</QTab>
|
42
|
+
</QTabs>
|
43
|
+
</div>
|
3
44
|
<div
|
4
45
|
v-for="(field, index) in mappedFields"
|
5
46
|
:key="field.id"
|
@@ -7,7 +48,8 @@
|
|
7
48
|
>
|
8
49
|
<Component
|
9
50
|
:is="field.component"
|
10
|
-
|
51
|
+
:key="field.name + '-' + currentVariation"
|
52
|
+
:model-value="getFieldValue(field.name)"
|
11
53
|
:field="field"
|
12
54
|
:label="field.label || undefined"
|
13
55
|
:no-label="noLabel"
|
@@ -17,10 +59,36 @@
|
|
17
59
|
@update:model-value="onInput(field.name, $event)"
|
18
60
|
/>
|
19
61
|
</div>
|
62
|
+
<ConfirmDialog
|
63
|
+
v-if="variationToEdit"
|
64
|
+
title="Change variation name"
|
65
|
+
@confirm="onChangeVariationName"
|
66
|
+
@close="variationToEdit = null"
|
67
|
+
>
|
68
|
+
<TextField
|
69
|
+
v-model="newVariationName"
|
70
|
+
label="Enter name"
|
71
|
+
placeholder="Variation Name"
|
72
|
+
input-class="bg-white"
|
73
|
+
/>
|
74
|
+
</ConfirmDialog>
|
75
|
+
<ConfirmDialog
|
76
|
+
v-if="variationToDelete"
|
77
|
+
:title="`Remove variation ${variationToDelete}?`"
|
78
|
+
content="You cannot undo this action. If there was any analytics collected for this variation, it will still be attributed to the ad."
|
79
|
+
confirm-class="bg-red-900 text-white"
|
80
|
+
content-class="w-96"
|
81
|
+
@confirm="onRemoveVariation(variationToDelete)"
|
82
|
+
@close="variationToDelete = ''"
|
83
|
+
/>
|
20
84
|
</div>
|
21
85
|
</template>
|
22
86
|
<script setup>
|
23
|
-
import {
|
87
|
+
import { PencilIcon as EditIcon } from "@heroicons/vue/solid";
|
88
|
+
import { computed, ref } from "vue";
|
89
|
+
import { FlashMessages, incrementName, replace } from "../../../helpers";
|
90
|
+
import { TrashIcon as RemoveIcon } from "../../../svg";
|
91
|
+
import { ConfirmDialog } from "../../Utility";
|
24
92
|
import {
|
25
93
|
BooleanField,
|
26
94
|
DateField,
|
@@ -36,17 +104,18 @@ import {
|
|
36
104
|
const emit = defineEmits(["update:values"]);
|
37
105
|
const props = defineProps({
|
38
106
|
values: {
|
39
|
-
type:
|
107
|
+
type: Array,
|
40
108
|
default: null
|
41
109
|
},
|
42
|
-
|
43
|
-
type:
|
110
|
+
form: {
|
111
|
+
type: Object,
|
44
112
|
required: true
|
45
113
|
},
|
46
114
|
noLabel: Boolean,
|
47
115
|
showName: Boolean,
|
48
116
|
disable: Boolean,
|
49
|
-
readonly: Boolean
|
117
|
+
readonly: Boolean,
|
118
|
+
saving: Boolean
|
50
119
|
});
|
51
120
|
|
52
121
|
const FORM_FIELD_MAP = {
|
@@ -61,16 +130,83 @@ const FORM_FIELD_MAP = {
|
|
61
130
|
WYSIWYG: WysiwygField
|
62
131
|
};
|
63
132
|
|
64
|
-
const mappedFields = props.fields.map((field) => ({
|
133
|
+
const mappedFields = props.form.fields.map((field) => ({
|
65
134
|
placeholder: `Enter ${field.label}`,
|
66
135
|
...field,
|
67
136
|
component: FORM_FIELD_MAP[field.type],
|
68
137
|
default: field.type === "BOOLEAN" ? false : ""
|
69
138
|
}));
|
70
139
|
|
71
|
-
const
|
140
|
+
const variationNames = computed(() => {
|
141
|
+
return [...new Set(props.values.map(v => v.variation))].sort();
|
142
|
+
});
|
143
|
+
|
144
|
+
const currentVariation = ref(variationNames.value[0] || "default");
|
145
|
+
const newVariationName = ref("");
|
146
|
+
const variationToEdit = ref("");
|
147
|
+
const variationToDelete = ref("");
|
148
|
+
const canAddVariation = computed(() => variationNames.value.length < props.form.variations && !props.readonly && !props.disable);
|
149
|
+
|
150
|
+
function getFieldResponse(name) {
|
151
|
+
if (!props.values) return undefined;
|
152
|
+
return props.values.find((v) => v.variation === currentVariation.value && v.name === name);
|
153
|
+
}
|
154
|
+
function getFieldValue(name) {
|
155
|
+
return getFieldResponse(name)?.value;
|
156
|
+
}
|
157
|
+
function onInput(name, value) {
|
158
|
+
const fieldResponse = getFieldResponse(name);
|
159
|
+
const newFieldResponse = {
|
160
|
+
name,
|
161
|
+
variation: currentVariation.value,
|
162
|
+
value
|
163
|
+
};
|
164
|
+
const newValues = replace(props.values, fieldResponse, newFieldResponse, true);
|
165
|
+
emit("update:values", newValues);
|
166
|
+
}
|
167
|
+
|
168
|
+
function onAddVariation() {
|
169
|
+
if (props.saving) return;
|
170
|
+
|
171
|
+
const previousName = variationNames.value[variationNames.value.length - 1];
|
172
|
+
const newName = incrementName(previousName === "default" ? "Variation" : previousName);
|
173
|
+
|
174
|
+
const newVariation = props.form.fields.map((field) => ({
|
175
|
+
variation: newName,
|
176
|
+
name: field.name,
|
177
|
+
value: field.type === "BOOLEAN" ? false : null
|
178
|
+
}));
|
179
|
+
const newValues = [...props.values, ...newVariation];
|
180
|
+
emit("update:values", newValues);
|
181
|
+
currentVariation.value = newName;
|
182
|
+
}
|
183
|
+
|
184
|
+
function onChangeVariationName() {
|
185
|
+
if (!newVariationName.value) return;
|
186
|
+
if (variationNames.value.includes(newVariationName.value)) {
|
187
|
+
FlashMessages.error("Variation name already exists");
|
188
|
+
return;
|
189
|
+
}
|
190
|
+
const newValues = props.values.map((v) => {
|
191
|
+
if (v.variation === variationToEdit.value) {
|
192
|
+
return { ...v, variation: newVariationName.value };
|
193
|
+
}
|
194
|
+
return v;
|
195
|
+
});
|
196
|
+
emit("update:values", newValues);
|
197
|
+
|
198
|
+
currentVariation.value = newVariationName.value;
|
199
|
+
variationToEdit.value = "";
|
200
|
+
newVariationName.value = "";
|
201
|
+
}
|
202
|
+
|
203
|
+
function onRemoveVariation(name) {
|
204
|
+
const newValues = props.values.filter((v) => v.variation !== name);
|
205
|
+
emit("update:values", newValues);
|
72
206
|
|
73
|
-
|
74
|
-
|
207
|
+
if (currentVariation.value === name) {
|
208
|
+
currentVariation.value = variationNames.value[0];
|
209
|
+
}
|
210
|
+
variationToDelete.value = "";
|
75
211
|
}
|
76
212
|
</script>
|
@@ -8,5 +8,6 @@ export * from "./tableColumns";
|
|
8
8
|
export { default as ActionMenu } from "./ActionMenu.vue";
|
9
9
|
export { default as ActionTable } from "./ActionTable.vue";
|
10
10
|
export { default as ActionTableColumn } from "./ActionTableColumn.vue";
|
11
|
+
export { default as ActionTableHeaderColumn } from "./ActionTableHeaderColumn.vue";
|
11
12
|
export { default as EmptyTableState } from "./EmptyTableState.vue";
|
12
13
|
export { default as TableSummaryRow } from "./TableSummaryRow.vue";
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import { computed, ref, watch } from "vue";
|
2
|
-
import { getItem, setItem, waitForRef } from "../../helpers";
|
1
|
+
import { computed, Ref, ref, ShallowRef, shallowRef, watch } from "vue";
|
2
|
+
import { ActionTargetItem, getItem, setItem, waitForRef } from "../../helpers";
|
3
3
|
import { getFilterFromUrl } from "./listHelpers";
|
4
4
|
|
5
|
-
interface ListActionsOptions {
|
5
|
+
export interface ListActionsOptions {
|
6
6
|
listRoute: Function;
|
7
7
|
summaryRoute?: Function | null;
|
8
8
|
filterFieldOptionsRoute?: Function | null;
|
@@ -13,6 +13,14 @@ interface ListActionsOptions {
|
|
13
13
|
refreshFilters?: boolean;
|
14
14
|
}
|
15
15
|
|
16
|
+
export interface PagedItems {
|
17
|
+
data: any[] | undefined;
|
18
|
+
meta: {
|
19
|
+
total: number;
|
20
|
+
last_page?: number;
|
21
|
+
} | undefined;
|
22
|
+
}
|
23
|
+
|
16
24
|
export function useListControls(name: string, {
|
17
25
|
listRoute,
|
18
26
|
summaryRoute = null,
|
@@ -25,14 +33,19 @@ export function useListControls(name: string, {
|
|
25
33
|
}: ListActionsOptions) {
|
26
34
|
let isInitialized = false;
|
27
35
|
const PAGE_SETTINGS_KEY = `${name}-pagination-settings`;
|
28
|
-
const pagedItems =
|
29
|
-
const filter = ref({});
|
36
|
+
const pagedItems: Ref<PagedItems | null> = shallowRef(null);
|
37
|
+
const filter: Ref<object | any> = ref({});
|
30
38
|
const globalFilter = ref({});
|
31
39
|
const showFilters = ref(false);
|
32
|
-
const selectedRows =
|
40
|
+
const selectedRows = shallowRef([]);
|
33
41
|
const isLoadingList = ref(false);
|
34
42
|
const isLoadingSummary = ref(false);
|
35
|
-
const summary =
|
43
|
+
const summary = shallowRef(null);
|
44
|
+
|
45
|
+
// The active ad for viewing / editing
|
46
|
+
const activeItem: ShallowRef<ActionTargetItem | null> = shallowRef(null);
|
47
|
+
// Controls the active panel (ie: tab) if rendering a panels drawer or similar
|
48
|
+
const activePanel = shallowRef(null);
|
36
49
|
|
37
50
|
// Filter fields are the field values available for the currently applied filter on Creative Groups
|
38
51
|
// (ie: all states available under the current filter)
|
@@ -86,7 +99,7 @@ export function useListControls(name: string, {
|
|
86
99
|
isLoadingSummary.value = true;
|
87
100
|
const summaryFilter = { id: null, ...filter.value, ...globalFilter.value };
|
88
101
|
if (selectedRows.value.length) {
|
89
|
-
summaryFilter.id = selectedRows.value.map((row) => row.id);
|
102
|
+
summaryFilter.id = selectedRows.value.map((row: { id: string }) => row.id);
|
90
103
|
}
|
91
104
|
summary.value = await summaryRoute(summaryFilter);
|
92
105
|
isLoadingSummary.value = false;
|
@@ -108,7 +121,7 @@ export function useListControls(name: string, {
|
|
108
121
|
* Watches for a filter URL parameter and applies the filter if it is set.
|
109
122
|
*/
|
110
123
|
function applyFilterFromUrl(url: string, filterFields = null) {
|
111
|
-
if (url.match(urlPattern)) {
|
124
|
+
if (urlPattern && url.match(urlPattern)) {
|
112
125
|
// A flat list of valid filterable field names
|
113
126
|
const validFilterKeys = filterFields?.value?.map(group => group.fields.map(field => field.name)).flat();
|
114
127
|
|
@@ -125,15 +138,32 @@ export function useListControls(name: string, {
|
|
125
138
|
|
126
139
|
// Set the reactive pager to map from the Laravel pagination to Quasar pagination
|
127
140
|
// and automatically update the list of ads
|
128
|
-
function setPagedItems(items) {
|
141
|
+
function setPagedItems(items: any[] | PagedItems) {
|
142
|
+
let data, meta;
|
143
|
+
|
129
144
|
if (Array.isArray(items)) {
|
130
|
-
|
145
|
+
data = items;
|
146
|
+
meta = { total: items.length };
|
147
|
+
|
131
148
|
} else {
|
132
|
-
|
133
|
-
|
134
|
-
quasarPagination.value.rowsNumber = items.meta.total;
|
135
|
-
}
|
149
|
+
data = items.data;
|
150
|
+
meta = items.meta;
|
136
151
|
}
|
152
|
+
|
153
|
+
// Update the Quasar pagination rows number if it is different from the total
|
154
|
+
if (meta && meta.total !== quasarPagination.value.rowsNumber) {
|
155
|
+
quasarPagination.value.rowsNumber = meta.total;
|
156
|
+
}
|
157
|
+
|
158
|
+
// Add a reactive isSaving property to each item (for performance reasons in checking saving state)
|
159
|
+
data = data.map((item: any) => {
|
160
|
+
// We want to keep the isSaving state if it is already set, as optimizations prevent reloading the
|
161
|
+
// components, and therefore reactivity is not responding to the new isSaving state
|
162
|
+
const oldItem = pagedItems.value?.data?.find(i => i.id === item.id);
|
163
|
+
return { ...item, isSaving: oldItem?.isSaving || ref(false) };
|
164
|
+
});
|
165
|
+
|
166
|
+
pagedItems.value = { data, meta };
|
137
167
|
}
|
138
168
|
|
139
169
|
/**
|
@@ -148,9 +178,12 @@ export function useListControls(name: string, {
|
|
148
178
|
*
|
149
179
|
* @param updatedItem
|
150
180
|
*/
|
151
|
-
function
|
181
|
+
function setItemInList(updatedItem: any) {
|
152
182
|
const data = pagedItems.value?.data?.map(item => (item.id === updatedItem.id && (item.updated_at === null || item.updated_at <= updatedItem.updated_at)) ? updatedItem : item);
|
153
|
-
|
183
|
+
setPagedItems({
|
184
|
+
data,
|
185
|
+
meta: { total: pagedItems.value.meta.total }
|
186
|
+
});
|
154
187
|
|
155
188
|
// Update the active item as well if it is set
|
156
189
|
if (activeItem.value?.id === updatedItem.id) {
|
@@ -160,10 +193,10 @@ export function useListControls(name: string, {
|
|
160
193
|
|
161
194
|
/**
|
162
195
|
* Loads more items into the list.
|
163
|
-
* @param index
|
164
|
-
* @param perPage
|
165
196
|
*/
|
166
|
-
async function loadMore(index, perPage = undefined) {
|
197
|
+
async function loadMore(index: number, perPage = undefined) {
|
198
|
+
if (!moreRoute) return;
|
199
|
+
|
167
200
|
const newItems = await moreRoute({
|
168
201
|
page: index + 1,
|
169
202
|
perPage,
|
@@ -171,7 +204,10 @@ export function useListControls(name: string, {
|
|
171
204
|
});
|
172
205
|
|
173
206
|
if (newItems && newItems.length > 0) {
|
174
|
-
|
207
|
+
setPagedItems({
|
208
|
+
data: [...pagedItems.value.data, ...newItems],
|
209
|
+
meta: { total: pagedItems.value.meta.total }
|
210
|
+
});
|
175
211
|
return true;
|
176
212
|
}
|
177
213
|
|
@@ -180,7 +216,6 @@ export function useListControls(name: string, {
|
|
180
216
|
|
181
217
|
/**
|
182
218
|
* Refreshes the list, summary, and filter field options.
|
183
|
-
* @returns {Promise<Awaited<void>[]>}
|
184
219
|
*/
|
185
220
|
async function refreshAll() {
|
186
221
|
return Promise.all([loadList(), loadSummary(), loadFilterFieldOptions(), getActiveItemDetails()]);
|
@@ -188,10 +223,8 @@ export function useListControls(name: string, {
|
|
188
223
|
|
189
224
|
/**
|
190
225
|
* Updates the settings in local storage
|
191
|
-
* @param key
|
192
|
-
* @param value
|
193
226
|
*/
|
194
|
-
function updateSettings(key, value) {
|
227
|
+
function updateSettings(key: string, value: any) {
|
195
228
|
const settings = getItem(PAGE_SETTINGS_KEY) || {};
|
196
229
|
settings[key] = value;
|
197
230
|
setItem(PAGE_SETTINGS_KEY, settings);
|
@@ -242,11 +275,6 @@ export function useListControls(name: string, {
|
|
242
275
|
setItem(PAGE_SETTINGS_KEY, settings);
|
243
276
|
}
|
244
277
|
|
245
|
-
// The active ad for viewing / editing
|
246
|
-
const activeItem = ref(null);
|
247
|
-
// Controls the active panel (ie: tab) if rendering a panels drawer or similar
|
248
|
-
const activePanel = ref(null);
|
249
|
-
|
250
278
|
/**
|
251
279
|
* Gets the additional details for the currently active item.
|
252
280
|
* (ie: data that is not normally loaded in the list because it is not needed for the list view)
|
@@ -261,7 +289,8 @@ export function useListControls(name: string, {
|
|
261
289
|
// NOTE: race conditions might allow the finished loading item to be different to the currently
|
262
290
|
// requested item
|
263
291
|
if (result?.id === activeItem.value?.id) {
|
264
|
-
|
292
|
+
const loadedItem = pagedItems.value?.data.find((i: { id: string }) => i.id === result.id);
|
293
|
+
activeItem.value = { ...result, isSaving: loadedItem.isSaving || ref(false) };
|
265
294
|
}
|
266
295
|
}
|
267
296
|
|
@@ -289,12 +318,13 @@ export function useListControls(name: string, {
|
|
289
318
|
/**
|
290
319
|
* Gets the next item in the list at the given offset (ie: 1 or -1) from the current position in the list of the
|
291
320
|
* selected item. If the next item is on a previous or next page, it will load the page first then select the item
|
292
|
-
* @param offset
|
293
|
-
* @returns {Promise<void>}
|
294
321
|
*/
|
295
|
-
async function getNextItem(offset) {
|
296
|
-
|
297
|
-
|
322
|
+
async function getNextItem(offset: number) {
|
323
|
+
if (!pagedItems.value) return;
|
324
|
+
|
325
|
+
const index = pagedItems.value.data.findIndex((i: { id: string }) => i.id === activeItem.value?.id);
|
326
|
+
if (index === undefined || index === null) return;
|
327
|
+
|
298
328
|
let nextIndex = index + offset;
|
299
329
|
|
300
330
|
// Load the previous page if the offset is before index 0
|
@@ -321,7 +351,7 @@ export function useListControls(name: string, {
|
|
321
351
|
}
|
322
352
|
}
|
323
353
|
|
324
|
-
activeItem.value = pagedItems.value
|
354
|
+
activeItem.value = pagedItems.value?.data[nextIndex];
|
325
355
|
}
|
326
356
|
|
327
357
|
// Initialize the list actions and load settings, lists, summaries, filter fields, etc.
|
@@ -358,6 +388,6 @@ export function useListControls(name: string, {
|
|
358
388
|
getNextItem,
|
359
389
|
activatePanel,
|
360
390
|
applyFilterFromUrl,
|
361
|
-
|
391
|
+
setItemInList
|
362
392
|
};
|
363
393
|
}
|
@@ -9,9 +9,9 @@ export interface TableColumn {
|
|
9
9
|
field: string,
|
10
10
|
format?: Function,
|
11
11
|
innerClass?: string | object,
|
12
|
-
style?: string,
|
13
|
-
headerStyle?: string,
|
14
|
-
|
12
|
+
style?: string | object,
|
13
|
+
headerStyle?: string | object,
|
14
|
+
isSavingRow?: boolean | Function,
|
15
15
|
label: string,
|
16
16
|
maxWidth?: number,
|
17
17
|
minWidth?: number,
|