quasar-ui-danx 0.0.43 → 0.0.45
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +1 -1
- package/src/components/ActionTable/ActionTable.vue +17 -51
- package/src/components/ActionTable/ActionTableColumn.vue +72 -0
- package/src/components/ActionTable/index.ts +1 -0
- package/src/components/AuditHistory/AuditHistoryItem.vue +54 -0
- package/src/components/AuditHistory/AuditHistoryItemValue.vue +60 -0
- package/src/components/AuditHistory/index.ts +2 -0
- package/src/components/PanelsDrawer/PanelsDrawer.vue +64 -0
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +25 -0
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +65 -0
- package/src/components/PanelsDrawer/index.ts +3 -0
- package/src/components/Utility/Controls/PreviousNextControls.vue +28 -0
- package/src/components/Utility/Controls/index.ts +1 -0
- package/src/components/Utility/Formats/IconWithTextFormat.vue +7 -3
- package/src/components/Utility/Tabs/BadgeTab.vue +32 -0
- package/src/components/Utility/Tabs/IndicatorTab.vue +40 -0
- package/src/components/Utility/Tabs/index.ts +2 -0
- package/src/components/Utility/Tools/ActionVnode.vue +23 -0
- package/src/components/Utility/Tools/RenderVnode.vue +5 -0
- package/src/components/Utility/Tools/index.ts +2 -2
- package/src/components/Utility/index.ts +2 -0
- package/src/components/index.ts +3 -1
- package/src/helpers/actions.ts +12 -12
- package/src/svg/SkipNextIcon.svg +5 -0
- package/src/svg/SkipPreviousIcon.svg +5 -0
- package/src/svg/WarningIcon.svg +5 -0
- package/src/svg/index.ts +3 -0
- package/src/components/Utility/Tools/ActionInputComponent.vue +0 -23
- package/src/components/Utility/Tools/RenderVNode.vue +0 -6
package/package.json
CHANGED
@@ -48,46 +48,17 @@
|
|
48
48
|
</q-th>
|
49
49
|
</template>
|
50
50
|
<template #body-cell="rowProps">
|
51
|
-
<
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
<RenderVNode
|
60
|
-
v-if="rowProps.col.vnode"
|
61
|
-
:vnode="rowProps.col.vnode(rowProps.row)"
|
62
|
-
/>
|
63
|
-
<RenderComponent
|
64
|
-
v-else-if="rowProps.col.component"
|
65
|
-
:params="[rowProps.row]"
|
66
|
-
:component="rowProps.col.component"
|
67
|
-
/>
|
68
|
-
<div v-else-if="rowProps.col.fieldList">
|
69
|
-
<div v-for="field in rowProps.col.fieldList" :key="field">
|
70
|
-
{{ rowProps.row[field] }}
|
71
|
-
</div>
|
72
|
-
</div>
|
73
|
-
<div v-else>
|
74
|
-
<slot v-bind="{name: rowProps.col.name, row: rowProps.row, value: rowProps.value}">
|
75
|
-
{{ rowProps.value }}
|
76
|
-
</slot>
|
77
|
-
</div>
|
78
|
-
<div v-if="rowProps.col.actions" class="flex-grow flex justify-end pl-2">
|
79
|
-
<ActionMenu
|
80
|
-
:actions="rowProps.col.actions"
|
81
|
-
:target="rowProps.row"
|
82
|
-
:loading="isSavingRow(rowProps.row)"
|
83
|
-
@action="(action) => $emit('action', action, rowProps.row)"
|
84
|
-
/>
|
85
|
-
</div>
|
86
|
-
</component>
|
87
|
-
</q-td>
|
51
|
+
<ActionTableColumn
|
52
|
+
:row-props="rowProps"
|
53
|
+
:settings="columnSettings[rowProps.col.name]"
|
54
|
+
:is-saving="isSavingRow(rowProps.row)"
|
55
|
+
@action="$emit('action', $event, rowProps.row)"
|
56
|
+
>
|
57
|
+
<slot />
|
58
|
+
</ActionTableColumn>
|
88
59
|
</template>
|
89
60
|
<template #bottom>
|
90
|
-
<
|
61
|
+
<ActionVnode />
|
91
62
|
</template>
|
92
63
|
</q-table>
|
93
64
|
</template>
|
@@ -97,8 +68,8 @@ import { ref } from 'vue';
|
|
97
68
|
import { getItem, setItem } from '../../helpers';
|
98
69
|
import { DragHandleIcon as RowResizeIcon } from '../../svg';
|
99
70
|
import { HandleDraggable } from '../DragAndDrop';
|
100
|
-
import {
|
101
|
-
import {
|
71
|
+
import { ActionVnode, mapSortBy } from '../index';
|
72
|
+
import { ActionTableColumn, EmptyTableState, registerStickyScrolling, TableSummaryRow } from './index';
|
102
73
|
|
103
74
|
defineEmits(['action', 'update:quasar-pagination', 'update:selected-rows']);
|
104
75
|
const props = defineProps({
|
@@ -147,19 +118,14 @@ registerStickyScrolling(actionTable);
|
|
147
118
|
const COLUMN_SETTINGS_KEY = `column-settings-${props.name}`;
|
148
119
|
const columnSettings = ref(getItem(COLUMN_SETTINGS_KEY) || {});
|
149
120
|
function onResizeColumn(column, val) {
|
150
|
-
columnSettings.value
|
121
|
+
columnSettings.value = {
|
122
|
+
...columnSettings.value,
|
123
|
+
[column.name]: {
|
124
|
+
width: Math.max(Math.min(val.distance + val.startDropZoneSize, column.maxWidth || 500), column.minWidth || 80)
|
125
|
+
}
|
126
|
+
};
|
151
127
|
setItem(COLUMN_SETTINGS_KEY, columnSettings.value);
|
152
128
|
}
|
153
|
-
function getColumnStyle(column) {
|
154
|
-
const width = columnSettings.value[column.name] || column.width;
|
155
|
-
|
156
|
-
if (width) {
|
157
|
-
return {
|
158
|
-
width: `${width}px`
|
159
|
-
};
|
160
|
-
}
|
161
|
-
return null;
|
162
|
-
}
|
163
129
|
|
164
130
|
function isSavingRow(row) {
|
165
131
|
if (!props.isSavingTarget) return false;
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<template>
|
2
|
+
<q-td :key="rowProps.key" :props="rowProps" :style="columnStyle">
|
3
|
+
<div
|
4
|
+
class="flex items-center flex-nowrap"
|
5
|
+
:class="columnClass"
|
6
|
+
>
|
7
|
+
<a
|
8
|
+
v-if="column.onClick"
|
9
|
+
class="flex-grow"
|
10
|
+
@click="column.onClick(row)"
|
11
|
+
>
|
12
|
+
<RenderVnode
|
13
|
+
v-if="column.vnode"
|
14
|
+
:vnode="column.vnode(row)"
|
15
|
+
/>
|
16
|
+
<slot v-else v-bind="{name: column.name, row, value}">
|
17
|
+
{{ value }}
|
18
|
+
</slot>
|
19
|
+
</a>
|
20
|
+
<div v-else class="flex-grow">
|
21
|
+
<RenderVnode
|
22
|
+
v-if="column.vnode"
|
23
|
+
:vnode="column.vnode(row)"
|
24
|
+
/>
|
25
|
+
<slot v-else v-bind="{name: column.name, row, value}">
|
26
|
+
{{ value }}
|
27
|
+
</slot>
|
28
|
+
</div>
|
29
|
+
<div v-if="column.actions" class="flex flex-shrink-0 pl-2">
|
30
|
+
<ActionMenu
|
31
|
+
:actions="column.actions"
|
32
|
+
:target="row"
|
33
|
+
:loading="isSaving"
|
34
|
+
@action="$emit('action', $event)"
|
35
|
+
/>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</q-td>
|
39
|
+
</template>
|
40
|
+
<script setup>
|
41
|
+
import { computed } from 'vue';
|
42
|
+
import { RenderVnode } from '../Utility';
|
43
|
+
import { ActionMenu } from './index';
|
44
|
+
|
45
|
+
defineEmits(['action']);
|
46
|
+
const props = defineProps({
|
47
|
+
rowProps: {
|
48
|
+
type: Object,
|
49
|
+
required: true
|
50
|
+
},
|
51
|
+
settings: {
|
52
|
+
type: Object,
|
53
|
+
default: null
|
54
|
+
},
|
55
|
+
isSaving: Boolean
|
56
|
+
});
|
57
|
+
|
58
|
+
const row = computed(() => props.rowProps.row);
|
59
|
+
const column = computed(() => props.rowProps.col);
|
60
|
+
const value = computed(() => props.rowProps.value);
|
61
|
+
|
62
|
+
const columnStyle = computed(() => {
|
63
|
+
const width = props.settings?.width || column.value.width;
|
64
|
+
return width ? { width: `${width}px` } : null;
|
65
|
+
});
|
66
|
+
|
67
|
+
const columnClass = computed(() => ({
|
68
|
+
'justify-end': column.value.align === 'right',
|
69
|
+
'justify-center': column.value.align === 'center',
|
70
|
+
'justify-start': column.value.align === 'left'
|
71
|
+
}));
|
72
|
+
</script>
|
@@ -6,5 +6,6 @@ export * from "./listHelpers";
|
|
6
6
|
export * from "./tableColumns";
|
7
7
|
export { default as ActionMenu } from "./ActionMenu.vue";
|
8
8
|
export { default as ActionTable } from "./ActionTable.vue";
|
9
|
+
export { default as ActionTableColumn } from "./ActionTableColumn.vue";
|
9
10
|
export { default as EmptyTableState } from "./EmptyTableState.vue";
|
10
11
|
export { default as TableSummaryRow } from "./TableSummaryRow.vue";
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="text-gray-shadow flex items-stretch flex-nowrap p-4">
|
3
|
+
<div class="flex-grow text-sm w-3/5 overflow-auto">
|
4
|
+
<h5>{{ change.label }} ({{ change.name }})</h5>
|
5
|
+
<div class="flex flex-nowrap items-center mt-4">
|
6
|
+
<div class="bg-red-light line-through p-2">
|
7
|
+
<AdHistoryItemValue
|
8
|
+
:type="change.type"
|
9
|
+
:value="change.oldValue"
|
10
|
+
/>
|
11
|
+
</div>
|
12
|
+
<div class="bg-green-plus-4 ml-2.5 p-2">
|
13
|
+
<AdHistoryItemValue
|
14
|
+
:type="change.type"
|
15
|
+
:value="change.newValue"
|
16
|
+
/>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<div class="ml-4 text-sm w-2/5">
|
21
|
+
<template v-if="item.user">
|
22
|
+
<div>{{ item.user.name }}</div>
|
23
|
+
<div>{{ item.user.email }}</div>
|
24
|
+
</template>
|
25
|
+
<div>{{ item.account }}</div>
|
26
|
+
<div>
|
27
|
+
<a v-if="item.audit_request_id" :href="novaUrl" target="_blank">{{ fLocalizedDateTime(item.timestamp) }}</a>
|
28
|
+
<template v-else>{{ fLocalizedDateTime(item.timestamp) }}</template>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</template>
|
33
|
+
<script setup>
|
34
|
+
import { computed } from 'vue';
|
35
|
+
import { fLocalizedDateTime } from '../../helpers';
|
36
|
+
import AdHistoryItemValue from './AuditHistoryItemValue';
|
37
|
+
|
38
|
+
const props = defineProps({
|
39
|
+
item: {
|
40
|
+
type: Object,
|
41
|
+
required: true
|
42
|
+
},
|
43
|
+
change: {
|
44
|
+
type: Object,
|
45
|
+
required: true
|
46
|
+
},
|
47
|
+
novaUrl: {
|
48
|
+
type: String,
|
49
|
+
default: '/nova'
|
50
|
+
}
|
51
|
+
});
|
52
|
+
|
53
|
+
const novaUrl = computed(() => props.novaUrl + `/resources/audit-requests/${props.item.audit_request_id}`);
|
54
|
+
</script>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="flex space-x-2">
|
3
|
+
<template v-if="type === 'SINGLE_FILE'">
|
4
|
+
<FilePreview
|
5
|
+
:image="value"
|
6
|
+
class="w-24"
|
7
|
+
/>
|
8
|
+
</template>
|
9
|
+
<template v-else-if="type === 'MULTI_FILE'">
|
10
|
+
<FilePreview
|
11
|
+
v-for="file in value"
|
12
|
+
:key="'file-' + file.id"
|
13
|
+
:image="file"
|
14
|
+
class="w-24 mb-2"
|
15
|
+
/>
|
16
|
+
</template>
|
17
|
+
<template v-else-if="type === 'WYSIWYG'">
|
18
|
+
<div v-html="value" />
|
19
|
+
</template>
|
20
|
+
<template v-else>
|
21
|
+
{{ format(value) }}
|
22
|
+
</template>
|
23
|
+
</div>
|
24
|
+
</template>
|
25
|
+
<script setup>
|
26
|
+
import { fCurrency, fDate, fLocalizedDateTime, fNumber } from '../../helpers';
|
27
|
+
import { FilePreview } from '../Utility';
|
28
|
+
|
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
|
+
}
|
38
|
+
});
|
39
|
+
|
40
|
+
function format(value) {
|
41
|
+
if (value === null || value === '' || value === undefined) {
|
42
|
+
return '';
|
43
|
+
}
|
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
|
+
}
|
57
|
+
|
58
|
+
return value;
|
59
|
+
}
|
60
|
+
</script>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
<template>
|
2
|
+
<ContentDrawer
|
3
|
+
position="right"
|
4
|
+
:show="true"
|
5
|
+
overlay
|
6
|
+
content-class="h-full"
|
7
|
+
title=""
|
8
|
+
@update:show="$emit('close')"
|
9
|
+
>
|
10
|
+
<div class="flex flex-col flex-nowrap h-full">
|
11
|
+
<div class="flex items-center px-6 py-4 border-b">
|
12
|
+
<div class="flex-grow">
|
13
|
+
<slot name="header" />
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div>
|
17
|
+
<QBtn @click="$emit('close')">
|
18
|
+
<CloseIcon class="w-4" />
|
19
|
+
</QBtn>
|
20
|
+
</div>
|
21
|
+
</div>
|
22
|
+
<div class="flex-grow overflow-hidden h-full">
|
23
|
+
<div class="flex items-stretch flex-nowrap h-full">
|
24
|
+
<div class="border-r w-[13.5em] overflow-y-auto">
|
25
|
+
<PanelsDrawerTabs
|
26
|
+
v-model="activePanel"
|
27
|
+
:panels="panels"
|
28
|
+
@update:model-value="$emit('update:model-value', $event)"
|
29
|
+
/>
|
30
|
+
</div>
|
31
|
+
<PanelsDrawerPanels :panels="panels" :active-panel="activePanel" :class="panelsClass" />
|
32
|
+
<div v-if="$slots['right-sidebar']" class="border-l overflow-y-auto">
|
33
|
+
<slot name="right-sidebar" />
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</ContentDrawer>
|
39
|
+
</template>
|
40
|
+
<script setup>
|
41
|
+
import { ref, watch } from 'vue';
|
42
|
+
import { XIcon as CloseIcon } from '../../svg';
|
43
|
+
import { ContentDrawer } from '../Utility';
|
44
|
+
import { PanelsDrawerPanels, PanelsDrawerTabs } from './index';
|
45
|
+
|
46
|
+
defineEmits(['update:model-value', 'close']);
|
47
|
+
const props = defineProps({
|
48
|
+
modelValue: {
|
49
|
+
type: String,
|
50
|
+
default: null
|
51
|
+
},
|
52
|
+
panelsClass: {
|
53
|
+
type: [Object, String],
|
54
|
+
default: 'w-[35.5rem]'
|
55
|
+
},
|
56
|
+
panels: {
|
57
|
+
type: Array,
|
58
|
+
required: true
|
59
|
+
}
|
60
|
+
});
|
61
|
+
|
62
|
+
const activePanel = ref(props.modelValue);
|
63
|
+
watch(() => props.modelValue, (value) => activePanel.value = value);
|
64
|
+
</script>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<template>
|
2
|
+
<QTabPanels
|
3
|
+
:model-value="activePanel"
|
4
|
+
class="overflow-y-auto bg-neutral-plus-7 h-full transition-all"
|
5
|
+
>
|
6
|
+
<QTabPanel v-for="panel in panels" :key="panel.name" :name="panel.name">
|
7
|
+
<RenderVnode v-if="panel.vnode" :vnode="panel.vnode" />
|
8
|
+
</QTabPanel>
|
9
|
+
</QTabPanels>
|
10
|
+
</template>
|
11
|
+
|
12
|
+
<script setup>
|
13
|
+
import { RenderVnode } from "quasar-ui-danx";
|
14
|
+
|
15
|
+
defineProps({
|
16
|
+
activePanel: {
|
17
|
+
type: String,
|
18
|
+
required: true
|
19
|
+
},
|
20
|
+
panels: {
|
21
|
+
type: Array,
|
22
|
+
required: true
|
23
|
+
}
|
24
|
+
});
|
25
|
+
</script>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<template>
|
2
|
+
<QTabs
|
3
|
+
:model-value="modelValue"
|
4
|
+
vertical
|
5
|
+
align="left"
|
6
|
+
class="panel-tabs p-4 h-auto"
|
7
|
+
no-caps
|
8
|
+
@update:model-value="$emit('update:model-value', $event)"
|
9
|
+
>
|
10
|
+
<template v-for="panel in panels">
|
11
|
+
<template v-if="panel.enabled !== false">
|
12
|
+
<RenderVnode
|
13
|
+
v-if="panel.tabVnode"
|
14
|
+
:key="panel.name"
|
15
|
+
:vnode="panel.tabVnode"
|
16
|
+
:is-active="modelValue === panel.name"
|
17
|
+
:name="panel.name"
|
18
|
+
:label="panel.label"
|
19
|
+
/>
|
20
|
+
<QTab v-else :key="panel.name" :name="panel.name" :label="panel.label" />
|
21
|
+
</template>
|
22
|
+
</template>
|
23
|
+
</QTabs>
|
24
|
+
</template>
|
25
|
+
<script setup>
|
26
|
+
import { QTab } from "quasar";
|
27
|
+
import { RenderVnode } from "quasar-ui-danx";
|
28
|
+
|
29
|
+
defineEmits(["update:model-value"]);
|
30
|
+
defineProps({
|
31
|
+
modelValue: {
|
32
|
+
type: String,
|
33
|
+
default: "general"
|
34
|
+
},
|
35
|
+
panels: {
|
36
|
+
type: Array,
|
37
|
+
required: true
|
38
|
+
}
|
39
|
+
});
|
40
|
+
</script>
|
41
|
+
|
42
|
+
<style
|
43
|
+
lang="scss"
|
44
|
+
scoped
|
45
|
+
>
|
46
|
+
.panel-tabs {
|
47
|
+
:deep(.q-tab) {
|
48
|
+
justify-content: start !important;
|
49
|
+
padding: 0;
|
50
|
+
@apply text-left py-2.5 px-2 rounded-lg hover:bg-neutral-plus-6;
|
51
|
+
|
52
|
+
.q-focus-helper, .q-tab__indicator {
|
53
|
+
display: none;
|
54
|
+
}
|
55
|
+
|
56
|
+
.q-tab__content {
|
57
|
+
@apply p-0;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
:deep(.q-tab.q-tab--active) {
|
62
|
+
@apply text-white bg-blue-base;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
</style>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="previous-next-controls">
|
3
|
+
<QBtn
|
4
|
+
class="bg-neutral-plus-6 border-neutral-plus-4 border border-solid !rounded-r-none !p-2 !min-w-0"
|
5
|
+
:disable="isLoading"
|
6
|
+
:loading="isLoading"
|
7
|
+
@click="$emit('next', -1)"
|
8
|
+
>
|
9
|
+
<SkipPreviousIcon class="w-6" />
|
10
|
+
</QBtn>
|
11
|
+
<QBtn
|
12
|
+
class="bg-neutral-plus-6 border-neutral-plus-4 border border-solid border-l-0 !rounded-l-none !p-2 !min-w-0"
|
13
|
+
:disable="isLoading"
|
14
|
+
:loading="isLoading"
|
15
|
+
@click="$emit('next', 1)"
|
16
|
+
>
|
17
|
+
<SkipNextIcon class="w-6" />
|
18
|
+
</QBtn>
|
19
|
+
</div>
|
20
|
+
</template>
|
21
|
+
<script setup>
|
22
|
+
import { SkipNextIcon, SkipPreviousIcon } from '../../../svg';
|
23
|
+
|
24
|
+
defineEmits(['next']);
|
25
|
+
defineProps({
|
26
|
+
isLoading: Boolean
|
27
|
+
});
|
28
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as PreviousNextControls } from "./PreviousNextControls.vue";
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<div class="flex items-center">
|
3
3
|
<component :is="icon" :class="iconClass" />
|
4
4
|
<div :class="textClass">
|
5
|
-
<slot
|
5
|
+
<slot>{{ text }}</slot>
|
6
6
|
</div>
|
7
7
|
</div>
|
8
8
|
</template>
|
@@ -14,11 +14,15 @@ defineProps({
|
|
14
14
|
},
|
15
15
|
iconClass: {
|
16
16
|
type: String,
|
17
|
-
default:
|
17
|
+
default: 'w-6'
|
18
|
+
},
|
19
|
+
text: {
|
20
|
+
type: String,
|
21
|
+
default: null
|
18
22
|
},
|
19
23
|
textClass: {
|
20
24
|
type: String,
|
21
|
-
default:
|
25
|
+
default: 'ml-2'
|
22
26
|
}
|
23
27
|
});
|
24
28
|
</script>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<template>
|
2
|
+
<q-tab
|
3
|
+
:name="name"
|
4
|
+
class="w-full"
|
5
|
+
content-class="w-full"
|
6
|
+
>
|
7
|
+
<div class="flex items-center w-full">
|
8
|
+
<div class="flex-grow text-sm">{{ label }}</div>
|
9
|
+
<q-badge
|
10
|
+
color="gray-base"
|
11
|
+
:label="count"
|
12
|
+
rounded
|
13
|
+
/>
|
14
|
+
</div>
|
15
|
+
</q-tab>
|
16
|
+
</template>
|
17
|
+
<script setup>
|
18
|
+
defineProps({
|
19
|
+
name: {
|
20
|
+
type: String,
|
21
|
+
required: true
|
22
|
+
},
|
23
|
+
label: {
|
24
|
+
type: String,
|
25
|
+
required: true
|
26
|
+
},
|
27
|
+
count: {
|
28
|
+
type: [String, Number],
|
29
|
+
default: ''
|
30
|
+
}
|
31
|
+
});
|
32
|
+
</script>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<template>
|
2
|
+
<q-tab
|
3
|
+
:name="name"
|
4
|
+
class="w-full"
|
5
|
+
content-class="w-full"
|
6
|
+
>
|
7
|
+
<div class="flex items-center w-full">
|
8
|
+
<div class="flex-grow text-sm">{{ label }}</div>
|
9
|
+
<div>
|
10
|
+
<OverdueIcon
|
11
|
+
v-if="overdue"
|
12
|
+
class="w-5 ml-2"
|
13
|
+
:class="isActive ? 'text-white' : 'text-red-danger'"
|
14
|
+
/>
|
15
|
+
<WarningIcon
|
16
|
+
v-else-if="warning"
|
17
|
+
class="text-yellow-warning w-5"
|
18
|
+
/>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
</q-tab>
|
22
|
+
</template>
|
23
|
+
<script setup>
|
24
|
+
import { ExclamationCircleIcon as OverdueIcon } from '@heroicons/vue/solid';
|
25
|
+
import { WarningIcon } from '../../../svg';
|
26
|
+
|
27
|
+
defineProps({
|
28
|
+
name: {
|
29
|
+
type: String,
|
30
|
+
required: true
|
31
|
+
},
|
32
|
+
label: {
|
33
|
+
type: String,
|
34
|
+
required: true
|
35
|
+
},
|
36
|
+
overdue: Boolean,
|
37
|
+
warning: Boolean,
|
38
|
+
isActive: Boolean
|
39
|
+
});
|
40
|
+
</script>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<RenderVnode
|
4
|
+
v-if="activeActionVnode"
|
5
|
+
:vnode="activeActionVnode.vnode"
|
6
|
+
:is-saving="isSaving"
|
7
|
+
@confirm="onConfirm"
|
8
|
+
@close="activeActionVnode.cancel"
|
9
|
+
/>
|
10
|
+
</div>
|
11
|
+
</template>
|
12
|
+
<script setup>
|
13
|
+
import { ref } from 'vue';
|
14
|
+
import { activeActionVnode } from '../../../helpers';
|
15
|
+
import { RenderVnode } from './index';
|
16
|
+
|
17
|
+
const isSaving = ref(false);
|
18
|
+
async function onConfirm(input) {
|
19
|
+
isSaving.value = true;
|
20
|
+
await activeActionVnode.value.confirm(input);
|
21
|
+
isSaving.value = false;
|
22
|
+
}
|
23
|
+
</script>
|
@@ -1,3 +1,3 @@
|
|
1
|
-
export { default as
|
1
|
+
export { default as ActionVnode } from "./ActionVnode.vue";
|
2
2
|
export { default as RenderComponent } from "./RenderComponent.vue";
|
3
|
-
export { default as
|
3
|
+
export { default as RenderVnode } from "./RenderVnode.vue";
|
@@ -1,8 +1,10 @@
|
|
1
1
|
export * from "./Buttons";
|
2
|
+
export * from "./Controls";
|
2
3
|
export * from "./Dialogs";
|
3
4
|
export * from "./Files";
|
4
5
|
export * from "./Formats";
|
5
6
|
export * from "./Layouts";
|
6
7
|
export * from "./Popovers";
|
8
|
+
export * from "./Tabs";
|
7
9
|
export * from "./Tools";
|
8
10
|
export * from "./Transitions";
|
package/src/components/index.ts
CHANGED
package/src/helpers/actions.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { shallowRef } from "vue";
|
1
|
+
import { shallowRef, VNode } from "vue";
|
2
2
|
import { FlashMessages } from "./index";
|
3
3
|
|
4
4
|
interface ActionOptions {
|
@@ -8,7 +8,7 @@ interface ActionOptions {
|
|
8
8
|
batch?: boolean;
|
9
9
|
category?: string;
|
10
10
|
class?: string;
|
11
|
-
|
11
|
+
vnode?: (target: object[] | object) => VNode;
|
12
12
|
enabled?: (target: object) => boolean;
|
13
13
|
batchEnabled?: (targets: object[]) => boolean;
|
14
14
|
onAction?: (action: string | null, target: object, input: any) => Promise<any>;
|
@@ -18,7 +18,7 @@ interface ActionOptions {
|
|
18
18
|
onFinish?: (action: string | null, targets: object, input: any) => any;
|
19
19
|
}
|
20
20
|
|
21
|
-
export const
|
21
|
+
export const activeActionVnode = shallowRef(null);
|
22
22
|
|
23
23
|
/**
|
24
24
|
* Hook to perform an action on a set of targets
|
@@ -98,25 +98,25 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
|
|
98
98
|
*/
|
99
99
|
async performAction(name: string | object, target: object[] | object, input: any = null) {
|
100
100
|
const action = resolveAction(name);
|
101
|
-
const
|
101
|
+
const vnode = action.vnode && action.vnode(target);
|
102
102
|
let result = null;
|
103
103
|
|
104
104
|
isSavingTarget.value = target;
|
105
105
|
|
106
|
-
// If
|
107
|
-
if (
|
108
|
-
// If the action requires an input, we set the
|
109
|
-
// This will tell the
|
106
|
+
// If additional input is required, first render the vnode and wait for the confirm or cancel action
|
107
|
+
if (vnode) {
|
108
|
+
// If the action requires an input, we set the activeActionVnode to the input component.
|
109
|
+
// This will tell the ActionVnode to render the input component, and confirm or cancel the
|
110
110
|
// action The confirm function has the input from the component passed and will resolve the promise
|
111
111
|
// with the result of the action
|
112
112
|
result = await new Promise((resolve, reject) => {
|
113
|
-
|
114
|
-
|
113
|
+
activeActionVnode.value = {
|
114
|
+
vnode,
|
115
115
|
confirm: async input => {
|
116
116
|
const result = await onConfirmAction(action, target, input);
|
117
117
|
|
118
118
|
// Only resolve when we have a non-error response, so we can show the error message w/o
|
119
|
-
// hiding the dialog /
|
119
|
+
// hiding the dialog / vnode
|
120
120
|
if (result === undefined || result === true || result?.success) {
|
121
121
|
resolve(result);
|
122
122
|
}
|
@@ -125,7 +125,7 @@ export function useActions(actions: ActionOptions[], globalOptions: ActionOption
|
|
125
125
|
};
|
126
126
|
});
|
127
127
|
|
128
|
-
|
128
|
+
activeActionVnode.value = null;
|
129
129
|
} else {
|
130
130
|
result = await onConfirmAction(action, target, input);
|
131
131
|
}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2
|
+
<path
|
3
|
+
d="M0 18.2727L10 1L20 18.2727H0ZM3.13636 16.4545H16.8636L10 4.63636L3.13636 16.4545ZM10 15.5455C10.2576 15.5455 10.4736 15.4582 10.6482 15.2836C10.8221 15.1097 10.9091 14.8939 10.9091 14.6364C10.9091 14.3788 10.8221 14.163 10.6482 13.9891C10.4736 13.8145 10.2576 13.7273 10 13.7273C9.74242 13.7273 9.52667 13.8145 9.35273 13.9891C9.17818 14.163 9.09091 14.3788 9.09091 14.6364C9.09091 14.8939 9.17818 15.1097 9.35273 15.2836C9.52667 15.4582 9.74242 15.5455 10 15.5455ZM9.09091 12.8182H10.9091V8.27273H9.09091V12.8182Z"
|
4
|
+
fill="currentColor"/>
|
5
|
+
</svg>
|
package/src/svg/index.ts
CHANGED
@@ -4,5 +4,8 @@ export { default as FilterIcon } from "./FilterIcon.svg";
|
|
4
4
|
export { default as ImageIcon } from "./ImageIcon.svg";
|
5
5
|
export { default as PdfIcon } from "./PdfIcon.svg";
|
6
6
|
export { default as PercentIcon } from "./PercentIcon.svg";
|
7
|
+
export { default as SkipNextIcon } from "./SkipNextIcon.svg";
|
8
|
+
export { default as SkipPreviousIcon } from "./SkipPreviousIcon.svg";
|
7
9
|
export { default as TrashIcon } from "./TrashIcon.svg";
|
10
|
+
export { default as WarningIcon } from "./WarningIcon.svg";
|
8
11
|
export { default as XIcon } from "./XIcon.svg";
|
@@ -1,23 +0,0 @@
|
|
1
|
-
<template>
|
2
|
-
<div>
|
3
|
-
<RenderComponent
|
4
|
-
v-if="activeActionInput"
|
5
|
-
:component="activeActionInput.component"
|
6
|
-
:is-saving="isSaving"
|
7
|
-
@confirm="onConfirm"
|
8
|
-
@close="activeActionInput.cancel"
|
9
|
-
/>
|
10
|
-
</div>
|
11
|
-
</template>
|
12
|
-
<script setup>
|
13
|
-
import { ref } from 'vue';
|
14
|
-
import { activeActionInput } from '../../../helpers';
|
15
|
-
import RenderComponent from './RenderComponent';
|
16
|
-
|
17
|
-
const isSaving = ref(false);
|
18
|
-
async function onConfirm(input) {
|
19
|
-
isSaving.value = true;
|
20
|
-
await activeActionInput.value.confirm(input);
|
21
|
-
isSaving.value = false;
|
22
|
-
}
|
23
|
-
</script>
|