pgo-ui 1.0.21 → 1.0.23
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/index.es.js +5386 -5331
- package/dist/index.umd.js +38 -38
- package/dist/pgo-ui.css +1 -1
- package/package.json +1 -1
- package/src/components/pgo/Card.vue +2 -2
- package/src/components/pgo/DataTable.vue +5 -5
- package/src/components/pgo/SearchBox.vue +14 -13
- package/src/components/pgo/base/Base.vue +1 -0
- package/src/components/pgo/forms/DynamicForm.vue +25 -16
- package/src/components/pgo/forms/Form.vue +2 -1
- package/src/main.js +0 -1
- package/src/pgo-components/lib/componentConfig.js +1 -1
- package/src/pgo-components/pages/ComponentRenderer.vue +2 -1
- package/src/pgo-components/pages/ListView copy.vue +385 -0
- package/src/pgo-components/pages/ListView.vue +214 -117
- package/src/pgo-components/pages/customSlot.vue +3 -0
package/package.json
CHANGED
|
@@ -90,12 +90,12 @@ const { language } = inject('i18n')
|
|
|
90
90
|
|
|
91
91
|
//footer props
|
|
92
92
|
footerClass: { type: String, default: '' },
|
|
93
|
-
footerBg: { type: String, default: '' },
|
|
93
|
+
footerBg: { type: String, default: 'vts-bg-surface' },
|
|
94
94
|
footerText: { type: String, default: 'text-input-text' },
|
|
95
95
|
footerTextSize: { type: String, default: '' },
|
|
96
96
|
footerBd: { type: String, default: '' },
|
|
97
97
|
footerMargin: { type: String, default: '' },
|
|
98
|
-
footerPadding: { type: String, default: 'px-
|
|
98
|
+
footerPadding: { type: String, default: 'px-4 py-4' },
|
|
99
99
|
|
|
100
100
|
})
|
|
101
101
|
|
|
@@ -388,6 +388,9 @@ import ConfirmationModal from './ConfirmationModal.vue'
|
|
|
388
388
|
|
|
389
389
|
import { textAlign, initializeFunctions } from '../../pgo-components/lib/componentConfig.js'
|
|
390
390
|
|
|
391
|
+
const api = inject('api')
|
|
392
|
+
const snackbar = inject('snackbar')
|
|
393
|
+
|
|
391
394
|
const props = defineProps({
|
|
392
395
|
// Data
|
|
393
396
|
items: {
|
|
@@ -658,8 +661,8 @@ const initializeAllFunctions = () => {
|
|
|
658
661
|
emit,
|
|
659
662
|
props,
|
|
660
663
|
console,
|
|
661
|
-
snackbar:
|
|
662
|
-
api:
|
|
664
|
+
snackbar: snackbar,
|
|
665
|
+
api: api,
|
|
663
666
|
this: {
|
|
664
667
|
variables: tableVariables
|
|
665
668
|
}
|
|
@@ -888,11 +891,8 @@ const confirmUpdate = async () => {
|
|
|
888
891
|
|
|
889
892
|
// If updateUrl is provided, send API request
|
|
890
893
|
if (props.updateUrl) {
|
|
891
|
-
const api = inject('api', null)
|
|
892
|
-
if (api) {
|
|
893
894
|
const url = `${props.updateUrl}/${item[props.itemKey]}`
|
|
894
895
|
await api.patch(url, updateData)
|
|
895
|
-
}
|
|
896
896
|
}
|
|
897
897
|
|
|
898
898
|
// Update local data
|
|
@@ -45,28 +45,29 @@
|
|
|
45
45
|
@focus="handleFocus"
|
|
46
46
|
@blur="handleBlur"
|
|
47
47
|
/>
|
|
48
|
-
|
|
49
|
-
type="button"
|
|
50
|
-
label="buttons.search"
|
|
51
|
-
variant="tonal"
|
|
52
|
-
prepend-icon="magnifying-glass"
|
|
53
|
-
size="xs"
|
|
54
|
-
icon-type="soild"
|
|
55
|
-
:icon-rotate="props.iconRotate ? props.iconRotate : (computedLang === 'dv' ? 90 : 0)"
|
|
56
|
-
:disabled="disabled || loading"
|
|
57
|
-
@click="handleSubmit"
|
|
58
|
-
/>
|
|
48
|
+
|
|
59
49
|
<!-- :icon-rotate="props.iconRotate ?? (computedLang === 'dv' ? 180 : 0)" -->
|
|
60
50
|
</div>
|
|
61
51
|
</template>
|
|
62
52
|
|
|
63
53
|
|
|
64
54
|
<!-- Loading spinner in append slot -->
|
|
65
|
-
<template
|
|
66
|
-
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
55
|
+
<template #append>
|
|
56
|
+
<svg v-if="loading" class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
67
57
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
68
58
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 714 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
69
59
|
</svg>
|
|
60
|
+
<Button
|
|
61
|
+
type="button"
|
|
62
|
+
label="buttons.search"
|
|
63
|
+
variant="tonal"
|
|
64
|
+
prepend-icon="magnifying-glass"
|
|
65
|
+
size="xs"
|
|
66
|
+
icon-type="soild"
|
|
67
|
+
:icon-rotate="props.iconRotate ? props.iconRotate : (computedLang === 'dv' ? 90 : 0)"
|
|
68
|
+
:disabled="disabled || loading"
|
|
69
|
+
@click="handleSubmit"
|
|
70
|
+
/>
|
|
70
71
|
</template>
|
|
71
72
|
</Base>
|
|
72
73
|
</div>
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
<HeroIcon :size="iconSizes[size]" :name="append" :type="iconType == 'outline' ? 'outline' : 'solid'" />
|
|
47
47
|
</slot>
|
|
48
48
|
</div>
|
|
49
|
+
<!-- <slot name="" -->
|
|
49
50
|
</div>
|
|
50
51
|
</div>
|
|
51
52
|
<div v-if="messagesToShow.length" :class="['.vts-mt-1 text-xs', selectLanguage === 'dv' ? 'text-right' : 'text-left', 'text-input-text']">
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
v-model="open"
|
|
4
4
|
persistent
|
|
5
5
|
padding="p-3"
|
|
6
|
+
:bg="bg"
|
|
6
7
|
>
|
|
7
8
|
<Banner
|
|
8
9
|
v-if="form?.banner"
|
|
9
10
|
v-bind="form?.banner"
|
|
10
|
-
bannerClass="mb-4"
|
|
11
|
+
bannerClass="mb-4"
|
|
11
12
|
/>
|
|
12
13
|
<Form
|
|
13
14
|
v-model="valid"
|
|
@@ -19,11 +20,12 @@
|
|
|
19
20
|
type="hidden"
|
|
20
21
|
/>
|
|
21
22
|
<!-- Render grouped fields -->
|
|
23
|
+
<!-- -->
|
|
22
24
|
<template v-if="form.groups">
|
|
23
25
|
<template v-for="(group, id) in form.groups" :key="id">
|
|
24
26
|
<div v-if="shouldShowGroup(group)" class="mb-4">
|
|
25
|
-
<div :class="['relative grid border border-gray-300 rounded px-4
|
|
26
|
-
<div v-if="group.title" :class="['absolute top-0 -translate-y-1/2 bg-
|
|
27
|
+
<div :class="['relative grid border border-gray-300 rounded px-4 pt-6 pb-4', bg, gridColsMap[group.numberOfColumns] || gridColsMap[2], 'gap-2']">
|
|
28
|
+
<div v-if="group.title" :class="['absolute top-0 -translate-y-1/2 bg-inherit px-2 text-sm font-semibold mb-2', lang === 'dv' ? 'right-4' : 'left-4']">
|
|
27
29
|
{{ group.title ? useLanguageSelected(group.title, lang) : '' }}
|
|
28
30
|
</div>
|
|
29
31
|
<template
|
|
@@ -90,7 +92,7 @@
|
|
|
90
92
|
</div>
|
|
91
93
|
</Form>
|
|
92
94
|
<template #footer>
|
|
93
|
-
<div class="flex justify-end gap-2">
|
|
95
|
+
<div class="flex justify-end gap-2 ">
|
|
94
96
|
<Button v-if="mode == 'edit'"
|
|
95
97
|
label="buttons.edit"
|
|
96
98
|
color="primary"
|
|
@@ -148,7 +150,9 @@
|
|
|
148
150
|
lang:{ type: String, default: 'dv' },
|
|
149
151
|
crudLink:{ type: String },
|
|
150
152
|
editItemId: { type: [String, Number], default: null },
|
|
151
|
-
mode: { type: String, default: 'create', enum: ['create', 'edit'] }
|
|
153
|
+
mode: { type: String, default: 'create', enum: ['create', 'edit'] },
|
|
154
|
+
|
|
155
|
+
bg: { type: String, default: 'bg-background-color' },
|
|
152
156
|
});
|
|
153
157
|
|
|
154
158
|
const api = inject('api');
|
|
@@ -234,17 +238,17 @@
|
|
|
234
238
|
formData[field.key] = false;
|
|
235
239
|
break;
|
|
236
240
|
case 'array':
|
|
237
|
-
formData[field.key] =
|
|
241
|
+
formData[field.key] = null;
|
|
238
242
|
break;
|
|
239
243
|
case 'object':
|
|
240
|
-
formData[field.key] =
|
|
244
|
+
formData[field.key] = null;
|
|
241
245
|
break;
|
|
242
246
|
case 'string':
|
|
243
247
|
default:
|
|
244
248
|
if (field.inputType === 'checkbox') {
|
|
245
249
|
formData[field.key] = false; // Default checkboxes to false
|
|
246
250
|
} else {
|
|
247
|
-
formData[field.key] =
|
|
251
|
+
formData[field.key] = null;
|
|
248
252
|
}
|
|
249
253
|
break;
|
|
250
254
|
}
|
|
@@ -721,7 +725,7 @@
|
|
|
721
725
|
return;
|
|
722
726
|
}
|
|
723
727
|
|
|
724
|
-
if (!props.crudLink) {
|
|
728
|
+
if (!props.form?.crudLink) {
|
|
725
729
|
console.error('No crudLink defined in form config');
|
|
726
730
|
snackbar?.show({ message: 'Form configuration error', variant: 'error' });
|
|
727
731
|
return;
|
|
@@ -733,8 +737,8 @@
|
|
|
733
737
|
// Filter out non-database fields before submission
|
|
734
738
|
const submitData = getDbFieldsOnly(formData);
|
|
735
739
|
|
|
736
|
-
const isFullUrl = /^https?:\/\//i.test(props.crudLink)
|
|
737
|
-
let url = isFullUrl ? props.crudLink : baseUrl + '/' + props.crudLink
|
|
740
|
+
const isFullUrl = /^https?:\/\//i.test(props.form?.crudLink)
|
|
741
|
+
let url = isFullUrl ? props.form?.crudLink : baseUrl + '/' + props.form?.crudLink
|
|
738
742
|
let completeUrl = ''
|
|
739
743
|
if (props.mode === 'edit' && props.editItemId) {
|
|
740
744
|
url += `/${props.editItemId}`;
|
|
@@ -754,7 +758,8 @@
|
|
|
754
758
|
|
|
755
759
|
} catch (error) {
|
|
756
760
|
console.error('Form submission error:', error);
|
|
757
|
-
|
|
761
|
+
const errorMessage = error.response?.data?.message || 'Form submission failed';
|
|
762
|
+
snackbar?.show({ message: errorMessage, variant: 'error' });
|
|
758
763
|
if (error.response?.status === 422 || error.response?.data?.errors) {
|
|
759
764
|
const backendErrors = error.response.data.errors;
|
|
760
765
|
|
|
@@ -765,7 +770,6 @@
|
|
|
765
770
|
}
|
|
766
771
|
|
|
767
772
|
const errorMessage = error.response.data.message || 'Validation failed';
|
|
768
|
-
snackbar?.show({ message: errorMessage, variant: 'error' });
|
|
769
773
|
emit('submit:error', error);
|
|
770
774
|
}
|
|
771
775
|
} finally {
|
|
@@ -806,8 +810,9 @@
|
|
|
806
810
|
}
|
|
807
811
|
|
|
808
812
|
try {
|
|
809
|
-
|
|
810
|
-
|
|
813
|
+
console.log('form:', props.form);
|
|
814
|
+
const isFullUrl = /^https?:\/\//i.test(props.form?.crudLink)
|
|
815
|
+
let url = isFullUrl ? props.form?.crudLink : baseUrl + '/' + props.form?.crudLink
|
|
811
816
|
|
|
812
817
|
const response = await api.get(`${url}/${props.editItemId}`)
|
|
813
818
|
|
|
@@ -856,6 +861,10 @@
|
|
|
856
861
|
if (newForm.functions) {
|
|
857
862
|
initializeAllFunctions();
|
|
858
863
|
}
|
|
864
|
+
// Only fetch data if we're in edit mode and have the necessary data
|
|
865
|
+
if (props.mode === 'edit' && props.editItemId && newForm.crudLink) {
|
|
866
|
+
fetchData();
|
|
867
|
+
}
|
|
859
868
|
}
|
|
860
869
|
}, { deep: true });
|
|
861
870
|
|
|
@@ -880,7 +889,7 @@
|
|
|
880
889
|
// Initialize on mount
|
|
881
890
|
onMounted(() => {
|
|
882
891
|
if (props.mode === 'edit' && props.editItemId) {
|
|
883
|
-
fetchData()
|
|
892
|
+
// fetchData()
|
|
884
893
|
} else {
|
|
885
894
|
initializeFormData()
|
|
886
895
|
}
|
package/src/main.js
CHANGED
|
@@ -62,7 +62,7 @@ export const colorMap = {
|
|
|
62
62
|
tonal: 'bg-blue-50 vts-text-info vts-transition-colors hover:bg-blue-100'
|
|
63
63
|
},
|
|
64
64
|
gray: {
|
|
65
|
-
contained: '
|
|
65
|
+
contained: 'bg-gray-200 text-gray-700 vts-transition-colors hover:bg-gray-300',
|
|
66
66
|
outlined: 'vts-border vts-border-color vts-text-secondary vts-bg-transparent vts-transition-colors vts-hover-bg-surface',
|
|
67
67
|
text: 'vts-text-secondary vts-bg-transparent vts-transition-colors vts-hover-bg-surface',
|
|
68
68
|
tonal: 'vts-bg-border-light vts-text-secondary vts-transition-colors vts-hover-bg-surface'
|
|
@@ -57,13 +57,14 @@ const fetchData = async (queryParams = {}) => {
|
|
|
57
57
|
// const response = await api.get(`/ccs/${modelName.value}?${params}`) // uncomment this line to use real API
|
|
58
58
|
// const response = person // comment this line to use real API
|
|
59
59
|
console
|
|
60
|
-
if (!response || !response.
|
|
60
|
+
if (!response || !response.componentSettings) {
|
|
61
61
|
console.error('Invalid response:', response)
|
|
62
62
|
errormsg.value = response.error
|
|
63
63
|
snackbar.show({ message: errormsg.value, variant: 'error' })
|
|
64
64
|
}
|
|
65
65
|
items.value = response
|
|
66
66
|
DisplayComponent.value = response.component
|
|
67
|
+
// DisplayComponent.value = componentName.value
|
|
67
68
|
|
|
68
69
|
console.log('DisplayComponent:', DisplayComponent.value)
|
|
69
70
|
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mx-auto m-4">
|
|
3
|
+
<template v-if="items">
|
|
4
|
+
<slot name="topSlot" />
|
|
5
|
+
|
|
6
|
+
<!-- <hr class="border-input-border my-2" /> -->
|
|
7
|
+
<Card
|
|
8
|
+
card-class="border-none"
|
|
9
|
+
bg="vts-bg-surface-elevated"
|
|
10
|
+
padding="p-3"
|
|
11
|
+
margin="mb-4"
|
|
12
|
+
shadow="shadow-lg"
|
|
13
|
+
>
|
|
14
|
+
<slot name="toolbar">
|
|
15
|
+
<Toolbar
|
|
16
|
+
v-if="items.componentSettings?.toolbar"
|
|
17
|
+
:elevation="false"
|
|
18
|
+
color="none"
|
|
19
|
+
variant="contained"
|
|
20
|
+
:title="useLanguageSelected(items.componentSettings?.toolbar?.title)"
|
|
21
|
+
:quick-filters="items.componentSettings?.toolbar?.filters"
|
|
22
|
+
:filter-section="items.componentSettings?.filterSection ? true : false"
|
|
23
|
+
:filter-values="quickFilterValues"
|
|
24
|
+
:filter-section-values="filterSectionValues"
|
|
25
|
+
@update:filter-values="quickFilterValues = $event"
|
|
26
|
+
@show-filter-section="showFilters = !showFilters"
|
|
27
|
+
@search="handleSearch"
|
|
28
|
+
@refresh="handleRefresh"
|
|
29
|
+
@create-button="handleCreateButton"
|
|
30
|
+
/>
|
|
31
|
+
</slot>
|
|
32
|
+
<Transition
|
|
33
|
+
enter-active-class="transition-all duration-300 ease-out"
|
|
34
|
+
enter-from-class="transform -translate-y-4 opacity-0"
|
|
35
|
+
enter-to-class="transform translate-y-0 opacity-100"
|
|
36
|
+
leave-active-class="transition-all duration-200 ease-in"
|
|
37
|
+
leave-from-class="transform translate-y-0 opacity-100"
|
|
38
|
+
leave-to-class="transform -translate-y-4 opacity-0"
|
|
39
|
+
>
|
|
40
|
+
|
|
41
|
+
<Card
|
|
42
|
+
v-if="items?.componentSettings?.filterSection && showFilters"
|
|
43
|
+
card-class="border-dashed"
|
|
44
|
+
rounded="lg"
|
|
45
|
+
bg=""
|
|
46
|
+
margin="mt-2"
|
|
47
|
+
>
|
|
48
|
+
<slot name="filterSection">
|
|
49
|
+
<FilterSection
|
|
50
|
+
dir=""
|
|
51
|
+
grid
|
|
52
|
+
:grid-columns="2"
|
|
53
|
+
v-model="filterSectionValues"
|
|
54
|
+
rounded=""
|
|
55
|
+
:filters="items.componentSettings.filterSection.filters"
|
|
56
|
+
:buttons="items.componentSettings.filterSection.buttons"
|
|
57
|
+
@search="handleSearch"
|
|
58
|
+
@submit="handleSearch"
|
|
59
|
+
@close="showFilters = false"
|
|
60
|
+
/>
|
|
61
|
+
</slot>
|
|
62
|
+
</Card>
|
|
63
|
+
</Transition>
|
|
64
|
+
</Card>
|
|
65
|
+
<slot name="table" >
|
|
66
|
+
<DataTable
|
|
67
|
+
v-if="props.items?.componentSettings?.table?.headers.length > 0"
|
|
68
|
+
dir=""
|
|
69
|
+
:items="tableData.data"
|
|
70
|
+
:settings="props.items?.componentSettings?.table"
|
|
71
|
+
:server-side-options="options"
|
|
72
|
+
:loading="tableLoading"
|
|
73
|
+
title=""
|
|
74
|
+
:lang="lang"
|
|
75
|
+
@update:options="handleOptionsUpdate"
|
|
76
|
+
:show-actions="items.componentSettings?.table?.showActions ?? true"
|
|
77
|
+
:show-view="items.componentSettings?.table?.actions?.showView ?? true"
|
|
78
|
+
:show-edit="items.componentSettings?.table?.actions?.showEdit ?? true"
|
|
79
|
+
:show-delete="items.componentSettings?.table?.actions?.showDelete ?? true"
|
|
80
|
+
:inline-edit="items.componentSettings?.table?.showInlineEdit ?? true"
|
|
81
|
+
@inline-update="handleInlineUpdate"
|
|
82
|
+
@view="handleView"
|
|
83
|
+
@edit="handleEdit"
|
|
84
|
+
@delete="handleDelete"
|
|
85
|
+
/>
|
|
86
|
+
<div v-else class="p-8">
|
|
87
|
+
<p>No headers found</p>
|
|
88
|
+
<!-- <pre>{{ JSON.stringify(items.componentSettings, null, 2) }}</pre> -->
|
|
89
|
+
</div>
|
|
90
|
+
</slot>
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
</template>
|
|
94
|
+
<div v-else class="flex items-center justify-center p-8">
|
|
95
|
+
<p>No data</p>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- FORM -->
|
|
99
|
+
<slot name="form">
|
|
100
|
+
<DynamicForm
|
|
101
|
+
v-if="showDynamicForm && items.componentSettings?.form"
|
|
102
|
+
:form="items.componentSettings?.form"
|
|
103
|
+
rounded="sm"
|
|
104
|
+
:title="useLanguageSelected(formMode == 'edit' ? items.componentSettings?.form?.editTitle : items.componentSettings?.form?.createTitle) || ''"
|
|
105
|
+
@close="showDynamicForm = false"
|
|
106
|
+
@submit:success="handleFormSubmit"
|
|
107
|
+
:lang="lang"
|
|
108
|
+
:crud-link="props.items?.componentSettings?.meta?.crudLink"
|
|
109
|
+
:edit-item-id="editItemId"
|
|
110
|
+
:mode="formMode"
|
|
111
|
+
/>
|
|
112
|
+
</slot>
|
|
113
|
+
<!-- Delete Confirmation Modal -->
|
|
114
|
+
<slot name="delete">
|
|
115
|
+
<ConfirmationModal
|
|
116
|
+
v-model="showConfirmation"
|
|
117
|
+
:item="DeleteItem"
|
|
118
|
+
:title="items.componentSettings?.table?.delete?.title || 'Delete Confirmation'"
|
|
119
|
+
:subtitle="items.componentSettings?.table?.delete?.subtitle || 'Are you sure you want to delete this item?'"
|
|
120
|
+
type="warning"
|
|
121
|
+
@confirm="handleDeleteConfirm"
|
|
122
|
+
@cancel="showConfirmation = false; DeleteItem = null"
|
|
123
|
+
>
|
|
124
|
+
<div
|
|
125
|
+
v-if="items.componentSettings?.table?.delete?.message"
|
|
126
|
+
v-for="(message, key) in items.componentSettings?.table?.delete?.message"
|
|
127
|
+
:key="key"
|
|
128
|
+
>
|
|
129
|
+
<p><b>{{ useLanguageSelected(message) }}:</b> {{ DeleteItem[key] }}</p>
|
|
130
|
+
</div>
|
|
131
|
+
</ConfirmationModal>
|
|
132
|
+
</slot>
|
|
133
|
+
<slot name="bottomSlot" />
|
|
134
|
+
</div>
|
|
135
|
+
</template>
|
|
136
|
+
|
|
137
|
+
<script setup>
|
|
138
|
+
import { ref, computed, watch, inject } from 'vue'
|
|
139
|
+
import { DynamicForm, Toolbar, FilterSection, DataTable, SearchBox, ConfirmationModal } from '../../components/pgo'
|
|
140
|
+
import { useLanguageSelected } from '../lib/componentConfig'
|
|
141
|
+
|
|
142
|
+
const api = inject('api')
|
|
143
|
+
const baseUrl = inject('baseUrl')
|
|
144
|
+
const snackbar = inject('snackbar')
|
|
145
|
+
// const route = useRoute()
|
|
146
|
+
|
|
147
|
+
const search = ref('')
|
|
148
|
+
|
|
149
|
+
const props = defineProps({
|
|
150
|
+
items: {
|
|
151
|
+
type: Object,
|
|
152
|
+
required: true
|
|
153
|
+
},
|
|
154
|
+
loading: {
|
|
155
|
+
type: Boolean,
|
|
156
|
+
default: false
|
|
157
|
+
},
|
|
158
|
+
lang:{ type: String, default: 'dv' }
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const tableLoading = ref(props.loading)
|
|
162
|
+
|
|
163
|
+
const emit = defineEmits(['update:queryParams', 'items'])
|
|
164
|
+
|
|
165
|
+
const showFilters = ref(false)
|
|
166
|
+
// const searchbar = ref({})
|
|
167
|
+
const filterSectionValues = ref({})
|
|
168
|
+
const quickFilterValues = ref({})
|
|
169
|
+
const tableData = ref({})
|
|
170
|
+
const showConfirmation = ref(false)
|
|
171
|
+
const DeleteItem = ref(null)
|
|
172
|
+
const showDynamicForm = ref(false)
|
|
173
|
+
const formMode = ref('')
|
|
174
|
+
const editItemId = ref(null)
|
|
175
|
+
|
|
176
|
+
// Manage options internally
|
|
177
|
+
const options = ref({
|
|
178
|
+
page: 1,
|
|
179
|
+
itemsPerPage: 10,
|
|
180
|
+
sortBy: [],
|
|
181
|
+
sortDesc: [],
|
|
182
|
+
itemsLength: 0
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const fetchData = async (queryParams = {}) => {
|
|
186
|
+
try {
|
|
187
|
+
tableLoading.value = true
|
|
188
|
+
|
|
189
|
+
// Build query string from params
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
...queryParams
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Get the datalink from props
|
|
195
|
+
const datalink = props.items?.componentSettings?.table?.datalink || ''
|
|
196
|
+
|
|
197
|
+
// Check if datalink is a full URL
|
|
198
|
+
const isFullUrl = /^https?:\/\//i.test(datalink)
|
|
199
|
+
const url = isFullUrl ? datalink : baseUrl + '/' + datalink
|
|
200
|
+
|
|
201
|
+
const response = await api.get(url + (params.toString() ? '&' + params.toString() : ''))
|
|
202
|
+
// const response = await api.get(`${props.items.componentSettings?.table.datalink}` + (params.toString() ? '&' + params.toString() : '')) // uncomment this line to use real API)
|
|
203
|
+
// const response = person // comment this line to use real API
|
|
204
|
+
tableData.value = response
|
|
205
|
+
if (response?.pagination) {
|
|
206
|
+
options.value = {
|
|
207
|
+
...options.value,
|
|
208
|
+
page: response.pagination.current_page,
|
|
209
|
+
itemsPerPage: response.pagination.per_page,
|
|
210
|
+
itemsLength: response?.pagination.total
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log('Table Fetched data:', tableData.value)
|
|
215
|
+
// snackbar.show({ message: 'Data fetched successfully', variant: 'success' })
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error fetching data:', error)
|
|
218
|
+
snackbar.show({ message: 'Error fetching data', variant: 'error' })
|
|
219
|
+
} finally {
|
|
220
|
+
tableLoading.value = false
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Update options from server response
|
|
225
|
+
watch(() => props.items, (newItems) => {
|
|
226
|
+
// Only fetch if componentSettings and table.datalink exist
|
|
227
|
+
if (newItems?.componentSettings?.table?.datalink) {
|
|
228
|
+
fetchData()
|
|
229
|
+
if (newItems?.componentSettings?.table?.pagination) {
|
|
230
|
+
options.value = {
|
|
231
|
+
...options.value,
|
|
232
|
+
page: newItems.componentSettings.table.pagination.current_page,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
emit('items', newItems)
|
|
237
|
+
}, { immediate: true, deep: true })
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// Handle options update from DataTable
|
|
241
|
+
const handleOptionsUpdate = (newOptions) => {
|
|
242
|
+
// console.log('Options updated:', newOptions)
|
|
243
|
+
|
|
244
|
+
// Update local options
|
|
245
|
+
options.value = { ...options.value, ...newOptions }
|
|
246
|
+
|
|
247
|
+
// Build query params
|
|
248
|
+
const queryParams = {
|
|
249
|
+
page: newOptions.page,
|
|
250
|
+
per_page: newOptions.itemsPerPage
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Add sorting if exists
|
|
254
|
+
if (newOptions.sortBy && newOptions.sortBy.length > 0) {
|
|
255
|
+
const sortParams = newOptions.sortBy.map((field, index) => {
|
|
256
|
+
const isDesc = newOptions.sortDesc[index]
|
|
257
|
+
return isDesc ? `-${field}` : field
|
|
258
|
+
})
|
|
259
|
+
queryParams.sort = sortParams.join(',')
|
|
260
|
+
}
|
|
261
|
+
fetchData(queryParams)
|
|
262
|
+
// Emit to parent ComponentRenderer
|
|
263
|
+
// emit('update:queryParams', queryParams)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const handleInlineUpdate = async ({ item, key, value, updateData }) => {
|
|
267
|
+
console.log('Inline update:', { item, key, value, updateData })
|
|
268
|
+
// Optionally refetch data after update
|
|
269
|
+
// await fetchData()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const handleSearch = (filters) => {
|
|
273
|
+
// Transform filters object into filter=key:value,key:value format
|
|
274
|
+
let filterString = ''
|
|
275
|
+
if (filters && typeof filters === 'object') {
|
|
276
|
+
const filterPairs = []
|
|
277
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
278
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
279
|
+
filterPairs.push(`${key}:${value}`)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
if (filterPairs.length > 0) {
|
|
283
|
+
filterString = filterPairs.join(',')
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Build query params
|
|
288
|
+
const queryParams = {
|
|
289
|
+
page: 1,
|
|
290
|
+
per_page: options.value.itemsPerPage
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (filterString) {
|
|
294
|
+
queryParams.filter = filterString
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Update options and emit
|
|
298
|
+
options.value = { ...options.value, page: 1 }
|
|
299
|
+
fetchData(queryParams)
|
|
300
|
+
// emit('update:queryParams', queryParams)
|
|
301
|
+
}
|
|
302
|
+
const handleFormSubmit = (msg) => {
|
|
303
|
+
snackbar.show({ message: msg, variant: 'success' })
|
|
304
|
+
showDynamicForm.value = false;
|
|
305
|
+
editItemId.value = null
|
|
306
|
+
handleRefresh()
|
|
307
|
+
}
|
|
308
|
+
const handleRefresh = (msg) => {
|
|
309
|
+
fetchData()
|
|
310
|
+
}
|
|
311
|
+
// Handle quick search from searchbar
|
|
312
|
+
const handleQuickSearch = (filters) => {
|
|
313
|
+
|
|
314
|
+
console.log('Quick search filters:', filters)
|
|
315
|
+
// Transform filters object into filter={columnName}:{value} format
|
|
316
|
+
const filterParams = {}
|
|
317
|
+
if (filters && typeof filters === 'object') {
|
|
318
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
319
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
320
|
+
filterParams[`filter[${key}]`] = value
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
// Reset to page 1 and fetch with filters
|
|
325
|
+
const newOptions = { ...options.value, ...filters, page: 1 }
|
|
326
|
+
options.value = newOptions
|
|
327
|
+
// emit('update:queryParams', newOptions)
|
|
328
|
+
fetchData(newOptions)
|
|
329
|
+
}
|
|
330
|
+
const handleCreateButton = () => {
|
|
331
|
+
editItemId.value = null
|
|
332
|
+
showDynamicForm.value = true
|
|
333
|
+
formMode.value = 'create'
|
|
334
|
+
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const handleView = (item) => {
|
|
338
|
+
// Navigate to detail view or open modal
|
|
339
|
+
}
|
|
340
|
+
const handleEdit = (item) => {
|
|
341
|
+
showDynamicForm.value = true
|
|
342
|
+
editItemId.value = item.id
|
|
343
|
+
formMode.value = 'edit'
|
|
344
|
+
// Navigate to detail view or open modal
|
|
345
|
+
}
|
|
346
|
+
const handleDelete = (item) => {
|
|
347
|
+
DeleteItem.value = item
|
|
348
|
+
showConfirmation.value = true
|
|
349
|
+
console.log('Delete item:', item)
|
|
350
|
+
// Navigate to detail view or open modal
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const handleDeleteConfirm = (item) => {
|
|
354
|
+
showConfirmation.value = false
|
|
355
|
+
console.log('Confirmed delete for item:', item)
|
|
356
|
+
deleteItemRequest(item.id)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const deleteItemRequest = async (id) => {
|
|
360
|
+
try {
|
|
361
|
+
// Get the delete link from componentSettings
|
|
362
|
+
const deleteLink = props.items?.componentSettings?.meta?.crudLink
|
|
363
|
+
if (!deleteLink) {
|
|
364
|
+
console.error('Delete link not defined in componentSettings')
|
|
365
|
+
snackbar.show({ message: 'Delete action not configured', variant: 'error' })
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Replace {id} in the template with the actual id
|
|
370
|
+
// const deleteLink = deleteLinkTemplate.replace('{id}', id)
|
|
371
|
+
|
|
372
|
+
// Check if deleteLink is a full URL
|
|
373
|
+
const isFullUrl = /^https?:\/\//i.test(deleteLink)
|
|
374
|
+
const url = isFullUrl ? deleteLink : baseUrl + '/' + deleteLink
|
|
375
|
+
|
|
376
|
+
await api.delete(url+`/${id}`)
|
|
377
|
+
snackbar.show({ message: 'Item deleted successfully', variant: 'success' })
|
|
378
|
+
fetchData() // Refresh the table after deletion
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Error deleting item:', error)
|
|
381
|
+
snackbar.show({ message: 'Error deleting item', variant: 'error' })
|
|
382
|
+
}
|
|
383
|
+
// Perform delete action, e.g. call API to delete item
|
|
384
|
+
}
|
|
385
|
+
</script>
|