sprintify-ui 0.0.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/README.md +188 -0
- package/dist/types/src/components/BaseAlert.vue.d.ts +51 -0
- package/dist/types/src/components/BaseAutocomplete.vue.d.ts +268 -0
- package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +273 -0
- package/dist/types/src/components/BaseAvatar.vue.d.ts +126 -0
- package/dist/types/src/components/BaseBadge.vue.d.ts +94 -0
- package/dist/types/src/components/BaseBelongsTo.vue.d.ts +268 -0
- package/dist/types/src/components/BaseBoolean.vue.d.ts +64 -0
- package/dist/types/src/components/BaseBreadcrumbs.vue.d.ts +66 -0
- package/dist/types/src/components/BaseButton.vue.d.ts +23 -0
- package/dist/types/src/components/BaseCard.vue.d.ts +74 -0
- package/dist/types/src/components/BaseCardRow.vue.d.ts +16 -0
- package/dist/types/src/components/BaseClipboard.vue.d.ts +74 -0
- package/dist/types/src/components/BaseContainer.vue.d.ts +34 -0
- package/dist/types/src/components/BaseCounter.vue.d.ts +125 -0
- package/dist/types/src/components/BaseDataIterator.vue.d.ts +345 -0
- package/dist/types/src/components/BaseDataTable.vue.d.ts +657 -0
- package/dist/types/src/components/BaseDataTableToggleColumns.vue.d.ts +1281 -0
- package/dist/types/src/components/BaseDatePicker.vue.d.ts +190 -0
- package/dist/types/src/components/BaseDateSelect.vue.d.ts +171 -0
- package/dist/types/src/components/BaseDescriptionList.vue.d.ts +48 -0
- package/dist/types/src/components/BaseDescriptionListItem.vue.d.ts +49 -0
- package/dist/types/src/components/BaseDialog.vue.d.ts +160 -0
- package/dist/types/src/components/BaseFilePicker.vue.d.ts +44 -0
- package/dist/types/src/components/BaseFileUploader.vue.d.ts +220 -0
- package/dist/types/src/components/BaseInput.vue.d.ts +209 -0
- package/dist/types/src/components/BaseInputLabel.vue.d.ts +31 -0
- package/dist/types/src/components/BaseLoadingCover.vue.d.ts +166 -0
- package/dist/types/src/components/BaseLoadingPage.vue.d.ts +2 -0
- package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +269 -0
- package/dist/types/src/components/BaseMediaLibraryItem.vue.d.ts +75 -0
- package/dist/types/src/components/BaseMenu.vue.d.ts +117 -0
- package/dist/types/src/components/BaseMenuItem.vue.d.ts +147 -0
- package/dist/types/src/components/BaseModalCenter.vue.d.ts +141 -0
- package/dist/types/src/components/BaseModalSide.vue.d.ts +141 -0
- package/dist/types/src/components/BaseNavbar.vue.d.ts +79 -0
- package/dist/types/src/components/BaseNavbarItem.vue.d.ts +80 -0
- package/dist/types/src/components/BaseNavbarItemContent.vue.d.ts +127 -0
- package/dist/types/src/components/BasePagination.vue.d.ts +25 -0
- package/dist/types/src/components/BasePaginationSimple.vue.d.ts +25 -0
- package/dist/types/src/components/BasePanel.vue.d.ts +31 -0
- package/dist/types/src/components/BasePassword.vue.d.ts +66 -0
- package/dist/types/src/components/BaseProcessRing.vue.d.ts +36 -0
- package/dist/types/src/components/BaseReadMore.vue.d.ts +74 -0
- package/dist/types/src/components/BaseSelect.vue.d.ts +55 -0
- package/dist/types/src/components/BaseSideNavigation.vue.d.ts +48 -0
- package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +92 -0
- package/dist/types/src/components/BaseSkeleton.vue.d.ts +93 -0
- package/dist/types/src/components/BaseSpinner.vue.d.ts +2 -0
- package/dist/types/src/components/BaseSwitch.vue.d.ts +39 -0
- package/dist/types/src/components/BaseSystemAlert.vue.d.ts +141 -0
- package/dist/types/src/components/BaseTabItem.vue.d.ts +70 -0
- package/dist/types/src/components/BaseTable.vue.d.ts +467 -0
- package/dist/types/src/components/BaseTableColumn.vue.d.ts +164 -0
- package/dist/types/src/components/BaseTabs.vue.d.ts +48 -0
- package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +274 -0
- package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +251 -0
- package/dist/types/src/components/BaseTextarea.vue.d.ts +228 -0
- package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +44 -0
- package/dist/types/src/components/BaseTitle.vue.d.ts +45 -0
- package/dist/types/src/components/BaseWordCount.vue.d.ts +31 -0
- package/dist/types/src/components/SlotComponent.d.ts +43 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/composables/breakpoints.d.ts +12 -0
- package/dist/types/src/composables/modal.d.ts +6 -0
- package/dist/types/src/constants/MyConstants.d.ts +1 -0
- package/dist/types/src/constants/index.d.ts +2 -0
- package/dist/types/src/index.d.ts +253 -0
- package/dist/types/src/types/Media.d.ts +8 -0
- package/dist/types/src/types/UploadedFile.d.ts +9 -0
- package/dist/types/src/types/User.d.ts +6 -0
- package/dist/types/src/types/types.d.ts +88 -0
- package/dist/types/src/utils/fileSizeFormat.d.ts +1 -0
- package/dist/types/src/utils/index.d.ts +4 -0
- package/dist/types/src/utils/scrollPreventer.d.ts +4 -0
- package/dist/types/src/utils/toHumanList.d.ts +1 -0
- package/package.json +99 -0
- package/src/assets/button.css +80 -0
- package/src/assets/form.css +15 -0
- package/src/assets/main.css +3 -0
- package/src/assets/pikaday.css +134 -0
- package/src/assets/tailwind.css +5 -0
- package/src/components/BaseAlert.stories.js +52 -0
- package/src/components/BaseAlert.vue +152 -0
- package/src/components/BaseAutocomplete.stories.js +127 -0
- package/src/components/BaseAutocomplete.vue +376 -0
- package/src/components/BaseAutocompleteFetch.stories.js +121 -0
- package/src/components/BaseAutocompleteFetch.vue +185 -0
- package/src/components/BaseAvatar.stories.js +39 -0
- package/src/components/BaseAvatar.vue +92 -0
- package/src/components/BaseBadge.stories.js +61 -0
- package/src/components/BaseBadge.vue +70 -0
- package/src/components/BaseBelongsTo.stories.js +130 -0
- package/src/components/BaseBelongsTo.vue +122 -0
- package/src/components/BaseBoolean.stories.js +35 -0
- package/src/components/BaseBoolean.vue +29 -0
- package/src/components/BaseBreadcrumbs.stories.js +45 -0
- package/src/components/BaseBreadcrumbs.vue +78 -0
- package/src/components/BaseButton.stories.js +80 -0
- package/src/components/BaseButton.vue +39 -0
- package/src/components/BaseCard.stories.js +61 -0
- package/src/components/BaseCard.vue +49 -0
- package/src/components/BaseCardRow.vue +34 -0
- package/src/components/BaseClipboard.stories.js +31 -0
- package/src/components/BaseClipboard.vue +96 -0
- package/src/components/BaseContainer.stories.js +34 -0
- package/src/components/BaseContainer.vue +50 -0
- package/src/components/BaseCounter.stories.js +32 -0
- package/src/components/BaseCounter.vue +72 -0
- package/src/components/BaseDataIterator.stories.js +90 -0
- package/src/components/BaseDataIterator.vue +658 -0
- package/src/components/BaseDataTable.stories.js +95 -0
- package/src/components/BaseDataTable.vue +489 -0
- package/src/components/BaseDataTableToggleColumns.vue +69 -0
- package/src/components/BaseDatePicker.stories.js +53 -0
- package/src/components/BaseDatePicker.vue +166 -0
- package/src/components/BaseDateSelect.vue +192 -0
- package/src/components/BaseDescriptionList.vue +11 -0
- package/src/components/BaseDescriptionListItem.vue +12 -0
- package/src/components/BaseDialog.vue +104 -0
- package/src/components/BaseFilePicker.vue +101 -0
- package/src/components/BaseFileUploader.vue +166 -0
- package/src/components/BaseInput.vue +82 -0
- package/src/components/BaseInputLabel.vue +26 -0
- package/src/components/BaseLoadingCover.vue +84 -0
- package/src/components/BaseLoadingPage.vue +19 -0
- package/src/components/BaseMediaLibrary.vue +281 -0
- package/src/components/BaseMediaLibraryItem.vue +92 -0
- package/src/components/BaseMenu.vue +114 -0
- package/src/components/BaseMenuItem.vue +93 -0
- package/src/components/BaseModalCenter.vue +107 -0
- package/src/components/BaseModalSide.vue +112 -0
- package/src/components/BaseNavbar.vue +72 -0
- package/src/components/BaseNavbarItem.vue +72 -0
- package/src/components/BaseNavbarItemContent.vue +57 -0
- package/src/components/BasePagination.vue +82 -0
- package/src/components/BasePaginationSimple.vue +60 -0
- package/src/components/BasePanel.vue +39 -0
- package/src/components/BasePassword.vue +73 -0
- package/src/components/BaseProcessRing.vue +56 -0
- package/src/components/BaseReadMore.vue +72 -0
- package/src/components/BaseSelect.vue +59 -0
- package/src/components/BaseSideNavigation.vue +7 -0
- package/src/components/BaseSideNavigationItem.vue +42 -0
- package/src/components/BaseSkeleton.vue +24 -0
- package/src/components/BaseSpinner.vue +47 -0
- package/src/components/BaseSwitch.vue +87 -0
- package/src/components/BaseSystemAlert.vue +86 -0
- package/src/components/BaseTabItem.vue +30 -0
- package/src/components/BaseTable.vue +781 -0
- package/src/components/BaseTableColumn.vue +109 -0
- package/src/components/BaseTabs.vue +12 -0
- package/src/components/BaseTagAutocomplete.vue +385 -0
- package/src/components/BaseTagAutocompleteFetch.vue +154 -0
- package/src/components/BaseTextarea.vue +73 -0
- package/src/components/BaseTextareaAutoresize.vue +117 -0
- package/src/components/BaseTitle.vue +80 -0
- package/src/components/BaseWordCount.vue +36 -0
- package/src/components/SlotComponent.ts +37 -0
- package/src/components/index.ts +5 -0
- package/src/composables/breakpoints.ts +6 -0
- package/src/composables/modal.ts +77 -0
- package/src/constants/MyConstants.ts +1 -0
- package/src/constants/index.ts +5 -0
- package/src/env.d.ts +15 -0
- package/src/index.ts +70 -0
- package/src/lang/en.json +56 -0
- package/src/lang/fr.json +56 -0
- package/src/types/Media.ts +9 -0
- package/src/types/UploadedFile.ts +10 -0
- package/src/types/User.ts +7 -0
- package/src/types/types.ts +112 -0
- package/src/utils/fileSizeFormat.ts +15 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/scrollPreventer.ts +21 -0
- package/src/utils/toHumanList.ts +20 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="dataIteratorNode">
|
|
3
|
+
<div
|
|
4
|
+
class="grid w-full max-w-full grid-flow-row gap-4"
|
|
5
|
+
:class="{
|
|
6
|
+
'grid-cols-[1fr_300px]': hasSidebar,
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
9
|
+
<div class="col-span-2 min-w-0 lg:col-span-1">
|
|
10
|
+
<!-- Header -->
|
|
11
|
+
<div class="mb-4 flex space-x-2 empty:mb-0">
|
|
12
|
+
<!-- Search bar -->
|
|
13
|
+
<div v-if="searchable" class="grow">
|
|
14
|
+
<div class="relative h-11">
|
|
15
|
+
<div
|
|
16
|
+
class="pointer-events-none absolute top-0 left-0 flex h-full items-center justify-center pl-2.5"
|
|
17
|
+
>
|
|
18
|
+
<Icon
|
|
19
|
+
class="h-6 w-6 text-slate-400"
|
|
20
|
+
icon="heroicons:magnifying-glass"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
<input
|
|
24
|
+
ref="searchInput"
|
|
25
|
+
v-model="searchQuery"
|
|
26
|
+
type="text"
|
|
27
|
+
class="h-11 w-full overflow-hidden rounded-md border border-slate-300 bg-white pl-10 pr-9 shadow-sm"
|
|
28
|
+
:placeholder="$t('sui.autocomplete_placeholder')"
|
|
29
|
+
@input="onSearch"
|
|
30
|
+
/>
|
|
31
|
+
<div
|
|
32
|
+
v-if="searchQuery"
|
|
33
|
+
class="absolute top-0 right-0 flex h-full items-center justify-center p-1"
|
|
34
|
+
>
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
class="flex appearance-none items-center rounded p-1 hover:bg-slate-100"
|
|
38
|
+
@click="
|
|
39
|
+
searchQuery = '';
|
|
40
|
+
onSearch('');
|
|
41
|
+
"
|
|
42
|
+
>
|
|
43
|
+
<Icon
|
|
44
|
+
class="h-6 w-6 text-slate-500"
|
|
45
|
+
icon="heroicons:x-mark"
|
|
46
|
+
/>
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Filters (mobile) -->
|
|
53
|
+
<button
|
|
54
|
+
v-if="mobileLayout && hasFilters"
|
|
55
|
+
class="btn flex h-11 items-center justify-center py-1 text-base"
|
|
56
|
+
type="button"
|
|
57
|
+
@click="showFilters = true"
|
|
58
|
+
>
|
|
59
|
+
<Icon
|
|
60
|
+
class="mr-2 h-6 w-6 text-slate-500"
|
|
61
|
+
icon="heroicons:adjustments-horizontal-solid"
|
|
62
|
+
/>
|
|
63
|
+
<span>{{ $t('sui.filters') }}</span>
|
|
64
|
+
</button>
|
|
65
|
+
|
|
66
|
+
<!-- Menu -->
|
|
67
|
+
<BaseMenu
|
|
68
|
+
v-if="actions && actions.length"
|
|
69
|
+
menu-class="w-52"
|
|
70
|
+
:items="actions"
|
|
71
|
+
>
|
|
72
|
+
<template #button="{ open }">
|
|
73
|
+
<div
|
|
74
|
+
class="flex h-11 w-11 items-center justify-center rounded-md border border-slate-300 bg-white shadow-sm duration-150 hover:bg-slate-50"
|
|
75
|
+
:class="[
|
|
76
|
+
open ? 'ring-2 ring-primary-500 ring-offset-2' : false,
|
|
77
|
+
]"
|
|
78
|
+
>
|
|
79
|
+
<Icon icon="heroicons-solid:dots-vertical" />
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
</BaseMenu>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<slot
|
|
86
|
+
:items="items"
|
|
87
|
+
:loading="loading"
|
|
88
|
+
:error="error"
|
|
89
|
+
:first-load="firstLoad"
|
|
90
|
+
:page="page"
|
|
91
|
+
:sort-field="sortField"
|
|
92
|
+
:sort-direction="sortDirection"
|
|
93
|
+
:on-sort-change="onSortChange"
|
|
94
|
+
:on-page-change="onPageChange"
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<!-- Pagination -->
|
|
98
|
+
|
|
99
|
+
<div class="mt-4">
|
|
100
|
+
<div
|
|
101
|
+
class="flex flex-wrap xs:flex-nowrap xs:items-center xs:justify-between"
|
|
102
|
+
>
|
|
103
|
+
<div class="flex items-center space-x-3">
|
|
104
|
+
<BasePaginationSimple
|
|
105
|
+
:model-value="page"
|
|
106
|
+
:last-page="lastPage"
|
|
107
|
+
@model-value:update="onPageChange"
|
|
108
|
+
/>
|
|
109
|
+
<div>
|
|
110
|
+
<p class="text-sm text-slate-600">
|
|
111
|
+
{{
|
|
112
|
+
(paginationMetadata.current_page - 1) *
|
|
113
|
+
paginationMetadata.per_page +
|
|
114
|
+
1
|
|
115
|
+
}}
|
|
116
|
+
-
|
|
117
|
+
{{
|
|
118
|
+
$t('sui.pagination_detail', {
|
|
119
|
+
page: Math.min(
|
|
120
|
+
paginationMetadata.current_page *
|
|
121
|
+
paginationMetadata.per_page,
|
|
122
|
+
paginationMetadata.total
|
|
123
|
+
),
|
|
124
|
+
total: paginationMetadata.total,
|
|
125
|
+
})
|
|
126
|
+
}}
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div v-if="!mobileLayout && hasSidebar">
|
|
135
|
+
<slot
|
|
136
|
+
name="sidebarTop"
|
|
137
|
+
:pagination-metadata="paginationMetadata"
|
|
138
|
+
></slot>
|
|
139
|
+
|
|
140
|
+
<div v-if="hasFilters" class="mb-4">
|
|
141
|
+
<BaseCard>
|
|
142
|
+
<BaseCardRow size="sm">
|
|
143
|
+
<div class="space-y-3">
|
|
144
|
+
<slot
|
|
145
|
+
name="filters"
|
|
146
|
+
:query="query"
|
|
147
|
+
:update-query="updateFilterQuery"
|
|
148
|
+
:update-query-value="updateFilterQueryValue"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</BaseCardRow>
|
|
152
|
+
</BaseCard>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<slot
|
|
156
|
+
name="sidebarBottom"
|
|
157
|
+
:pagination-metadata="paginationMetadata"
|
|
158
|
+
></slot>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<BaseModalSide v-if="hasFilters" v-model="showFilters">
|
|
163
|
+
<div class="px-4 py-5">
|
|
164
|
+
<h2 class="mb-4 font-semibold">
|
|
165
|
+
{{ $t('sui.filters') }}
|
|
166
|
+
</h2>
|
|
167
|
+
|
|
168
|
+
<div class="space-y-3">
|
|
169
|
+
<slot
|
|
170
|
+
name="filters"
|
|
171
|
+
:query="query"
|
|
172
|
+
:update-query="updateFilterQuery"
|
|
173
|
+
:update-query-value="updateFilterQueryValue"
|
|
174
|
+
/>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div>
|
|
178
|
+
<button class="btn btn-primary mt-4" @click="showFilters = false">
|
|
179
|
+
{{ $t('sui.apply_filters') }}
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</BaseModalSide>
|
|
184
|
+
</div>
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<script lang="ts">
|
|
188
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
189
|
+
|
|
190
|
+
type Direction = 'asc' | 'desc';
|
|
191
|
+
|
|
192
|
+
const DEFAULT_QUERY = {
|
|
193
|
+
page: 1,
|
|
194
|
+
search: '',
|
|
195
|
+
sort: '',
|
|
196
|
+
filter: {},
|
|
197
|
+
} as DataTableQuery;
|
|
198
|
+
</script>
|
|
199
|
+
|
|
200
|
+
<script lang="ts" setup>
|
|
201
|
+
import { cloneDeep, debounce, isArray, merge, set } from 'lodash';
|
|
202
|
+
import hash from 'object-hash';
|
|
203
|
+
import { ComputedRef, PropType, Ref } from 'vue';
|
|
204
|
+
import { useBreakpoints } from '@/composables/breakpoints';
|
|
205
|
+
import {
|
|
206
|
+
Collection,
|
|
207
|
+
DataTableQuery,
|
|
208
|
+
MenuItemInterface,
|
|
209
|
+
PaginatedCollection,
|
|
210
|
+
PaginationMetadata,
|
|
211
|
+
ResourceCollection,
|
|
212
|
+
} from '@/types/types';
|
|
213
|
+
|
|
214
|
+
import BaseMenu from './BaseMenu.vue';
|
|
215
|
+
import BaseCard from './BaseCard.vue';
|
|
216
|
+
import BaseCardRow from './BaseCardRow.vue';
|
|
217
|
+
import BasePaginationSimple from './BasePaginationSimple.vue';
|
|
218
|
+
import BaseModalSide from './BaseModalSide.vue';
|
|
219
|
+
import { config } from 'src';
|
|
220
|
+
|
|
221
|
+
const props = defineProps({
|
|
222
|
+
/**
|
|
223
|
+
* Base URL from which to make requests
|
|
224
|
+
*/
|
|
225
|
+
url: {
|
|
226
|
+
required: true,
|
|
227
|
+
type: String,
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Route key name for Laravel route model binding
|
|
232
|
+
*/
|
|
233
|
+
routeKeyName: {
|
|
234
|
+
default: 'id',
|
|
235
|
+
type: String,
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Query params that always get applied.
|
|
240
|
+
* To add overwrite-able query params, use defaultQuery.
|
|
241
|
+
*/
|
|
242
|
+
urlQuery: {
|
|
243
|
+
default: undefined,
|
|
244
|
+
type: Object as PropType<Record<string, any>>,
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Query params that gets applied by default.
|
|
249
|
+
* These may be overwritten by URL params generated by the data-table or filters
|
|
250
|
+
* To add query params that are always active, use urlQuery.
|
|
251
|
+
*/
|
|
252
|
+
defaultQuery: {
|
|
253
|
+
default: function () {
|
|
254
|
+
return DEFAULT_QUERY;
|
|
255
|
+
},
|
|
256
|
+
type: Object as PropType<DataTableQuery>,
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Add a search bar.
|
|
261
|
+
*/
|
|
262
|
+
searchable: {
|
|
263
|
+
default: true,
|
|
264
|
+
type: Boolean,
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Configure contextual actions.
|
|
269
|
+
*/
|
|
270
|
+
actions: {
|
|
271
|
+
default: undefined,
|
|
272
|
+
type: Array as PropType<MenuItemInterface[]>,
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Save data table state in URL.
|
|
277
|
+
*/
|
|
278
|
+
historyMode: {
|
|
279
|
+
default: true,
|
|
280
|
+
type: Boolean,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const http = config.http;
|
|
285
|
+
|
|
286
|
+
defineEmits([
|
|
287
|
+
'click',
|
|
288
|
+
'delete',
|
|
289
|
+
'checkAll',
|
|
290
|
+
'update:checked-rows',
|
|
291
|
+
'check',
|
|
292
|
+
'update:query',
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const dataIteratorNode = ref(null) as Ref<null | HTMLElement>;
|
|
296
|
+
const searchInput = ref(null) as Ref<null | HTMLInputElement>;
|
|
297
|
+
|
|
298
|
+
const route = useRoute();
|
|
299
|
+
const router = useRouter();
|
|
300
|
+
const routeName = route.name;
|
|
301
|
+
|
|
302
|
+
const breakpoints = useBreakpoints();
|
|
303
|
+
|
|
304
|
+
/** Data table state */
|
|
305
|
+
|
|
306
|
+
const firstLoad = ref(false);
|
|
307
|
+
const loading = ref(true);
|
|
308
|
+
const error = ref(false);
|
|
309
|
+
const showFilters = ref(false);
|
|
310
|
+
const searchQuery = ref('');
|
|
311
|
+
|
|
312
|
+
let lastUrl = '';
|
|
313
|
+
let lastQueryHash = '';
|
|
314
|
+
const query = ref(cloneDeep(props.defaultQuery)) as Ref<DataTableQuery>;
|
|
315
|
+
const slots = useSlots();
|
|
316
|
+
|
|
317
|
+
const mobileLayout = computed(() => {
|
|
318
|
+
return breakpoints.smaller('lg').value;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const hasFilters = computed((): boolean => {
|
|
322
|
+
const numberOfFilterSlots = slots.filters;
|
|
323
|
+
return numberOfFilterSlots !== undefined;
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const hasSidebar = computed(() => {
|
|
327
|
+
return (
|
|
328
|
+
hasFilters.value ||
|
|
329
|
+
slots.sidebarTop !== undefined ||
|
|
330
|
+
slots.sidebarBottom !== undefined
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/*
|
|
335
|
+
|--------------------------------------------------------------------------
|
|
336
|
+
| Query params
|
|
337
|
+
|--------------------------------------------------------------------------
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
function updateFilterQueryValue(key: string, value: any) {
|
|
341
|
+
let newQuery = cloneDeep(query.value);
|
|
342
|
+
newQuery = set(newQuery, key, value);
|
|
343
|
+
newQuery = set(newQuery, 'page', 1);
|
|
344
|
+
updateQuery(newQuery);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function updateFilterQuery(newQuery: DataTableQuery) {
|
|
348
|
+
newQuery = set(newQuery, 'page', 1);
|
|
349
|
+
updateQuery(newQuery);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function updateQuery(newQuery: DataTableQuery) {
|
|
353
|
+
if (!props.historyMode) {
|
|
354
|
+
query.value = newQuery;
|
|
355
|
+
fetch();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const newRoute = router.resolve({
|
|
360
|
+
path: route.path,
|
|
361
|
+
params: route.params,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const newParams = config.formatQueryString(newQuery);
|
|
365
|
+
const newRoutePath = newRoute.fullPath + '?' + newParams;
|
|
366
|
+
|
|
367
|
+
const oldParamString = getRouteQuery();
|
|
368
|
+
const oldParams = config.formatQueryString(oldParamString);
|
|
369
|
+
|
|
370
|
+
// Push new route if different
|
|
371
|
+
if (oldParams != newParams) {
|
|
372
|
+
if (!firstLoad.value) {
|
|
373
|
+
router.replace(newRoutePath);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
router.push(newRoutePath);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// If the URL is unchanged, we must manually trigger the fetch() method
|
|
381
|
+
// on first load.
|
|
382
|
+
|
|
383
|
+
if (!firstLoad.value) {
|
|
384
|
+
query.value = newQuery;
|
|
385
|
+
fetch();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function queryHash(query: DataTableQuery): string {
|
|
390
|
+
return hash(query);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/*
|
|
394
|
+
|--------------------------------------------------------------------------
|
|
395
|
+
| Data fetching
|
|
396
|
+
|--------------------------------------------------------------------------
|
|
397
|
+
*/
|
|
398
|
+
|
|
399
|
+
const url = computed(() => {
|
|
400
|
+
return props.url;
|
|
401
|
+
}) as ComputedRef<string>;
|
|
402
|
+
|
|
403
|
+
/*
|
|
404
|
+
|--------------------------------------------------------------------------
|
|
405
|
+
| Handlers
|
|
406
|
+
|--------------------------------------------------------------------------
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
function onPageChange(p: number) {
|
|
410
|
+
const newQuery = cloneDeep(query.value);
|
|
411
|
+
|
|
412
|
+
newQuery.page = p;
|
|
413
|
+
|
|
414
|
+
updateQuery(newQuery);
|
|
415
|
+
|
|
416
|
+
scrollIntoView();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function onSortChange(field: string, direction: Direction) {
|
|
420
|
+
let newSort = field;
|
|
421
|
+
|
|
422
|
+
if (newSort && direction == 'desc') {
|
|
423
|
+
newSort = '-' + newSort;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const newQuery = cloneDeep(query.value);
|
|
427
|
+
|
|
428
|
+
newQuery.page = 1;
|
|
429
|
+
newQuery.sort = newSort;
|
|
430
|
+
|
|
431
|
+
updateQuery(newQuery);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const onSearch = debounce((event: any) => {
|
|
435
|
+
const newQuery = cloneDeep(query.value);
|
|
436
|
+
|
|
437
|
+
newQuery.page = 1;
|
|
438
|
+
newQuery.search = searchQuery.value;
|
|
439
|
+
|
|
440
|
+
updateQuery(newQuery);
|
|
441
|
+
}, 350);
|
|
442
|
+
|
|
443
|
+
/*
|
|
444
|
+
|--------------------------------------------------------------------------
|
|
445
|
+
| Route watcher
|
|
446
|
+
|--------------------------------------------------------------------------
|
|
447
|
+
*/
|
|
448
|
+
|
|
449
|
+
watch(
|
|
450
|
+
() => route.query,
|
|
451
|
+
() => {
|
|
452
|
+
onRouteChange();
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
function getRouteQuery() {
|
|
457
|
+
return config.parseQueryString(window.location.search.replace(/^(\?)/, ''));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function onRouteChange() {
|
|
461
|
+
if (!props.historyMode) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Stop if route was changed
|
|
466
|
+
if (route.name != routeName) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const routeQuery = getRouteQuery();
|
|
471
|
+
const newQuery = routeQuery as DataTableQuery;
|
|
472
|
+
|
|
473
|
+
const newQueryHash = queryHash(newQuery);
|
|
474
|
+
|
|
475
|
+
if (newQueryHash == lastQueryHash) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
lastQueryHash = newQueryHash;
|
|
480
|
+
|
|
481
|
+
query.value = newQuery;
|
|
482
|
+
|
|
483
|
+
// Update search input if not in focus
|
|
484
|
+
if (searchInput.value && searchInput.value !== document.activeElement) {
|
|
485
|
+
updateSearchInput();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fetch();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/*
|
|
492
|
+
|--------------------------------------------------------------------------
|
|
493
|
+
| Fetch
|
|
494
|
+
|--------------------------------------------------------------------------
|
|
495
|
+
*/
|
|
496
|
+
|
|
497
|
+
function fetch(force = false) {
|
|
498
|
+
const urlSplit = url.value.split(/[?#]/);
|
|
499
|
+
|
|
500
|
+
const baseUrl = urlSplit[0];
|
|
501
|
+
const urlQueryString = urlSplit[1] ?? null;
|
|
502
|
+
const urlQuery = config.parseQueryString(urlQueryString);
|
|
503
|
+
|
|
504
|
+
const allParams = merge(
|
|
505
|
+
cloneDeep(query.value),
|
|
506
|
+
cloneDeep(props.urlQuery),
|
|
507
|
+
cloneDeep(urlQuery)
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
const queryString = config.formatQueryString(allParams);
|
|
511
|
+
|
|
512
|
+
const fullUrl = baseUrl + '?' + queryString;
|
|
513
|
+
|
|
514
|
+
if (lastUrl == fullUrl && !force) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
loading.value = true;
|
|
519
|
+
lastUrl = fullUrl;
|
|
520
|
+
|
|
521
|
+
http
|
|
522
|
+
.get(fullUrl)
|
|
523
|
+
.then((response) => {
|
|
524
|
+
data.value = response.data;
|
|
525
|
+
loading.value = false;
|
|
526
|
+
error.value = false;
|
|
527
|
+
firstLoad.value = true;
|
|
528
|
+
})
|
|
529
|
+
.catch(() => {
|
|
530
|
+
loading.value = false;
|
|
531
|
+
error.value = true;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/*
|
|
536
|
+
|--------------------------------------------------------------------------
|
|
537
|
+
| Data parsing
|
|
538
|
+
|--------------------------------------------------------------------------
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
const data = ref(null) as Ref<
|
|
542
|
+
null | ResourceCollection | PaginatedCollection | Collection
|
|
543
|
+
>;
|
|
544
|
+
|
|
545
|
+
const items = computed(() => {
|
|
546
|
+
if (!data.value) {
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
if (isArray(data.value)) {
|
|
550
|
+
return data.value;
|
|
551
|
+
}
|
|
552
|
+
return data.value.data;
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const paginationMetadata = computed(() => {
|
|
556
|
+
const defaultMeta = {
|
|
557
|
+
current_page: 1,
|
|
558
|
+
last_page: 1,
|
|
559
|
+
per_page: 1,
|
|
560
|
+
total: 0,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
if (!data.value) {
|
|
564
|
+
return defaultMeta;
|
|
565
|
+
}
|
|
566
|
+
if (isArray(data.value)) {
|
|
567
|
+
return defaultMeta;
|
|
568
|
+
}
|
|
569
|
+
if ('meta' in data.value) {
|
|
570
|
+
return data.value.meta;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
current_page: data.value.current_page,
|
|
574
|
+
last_page: data.value.last_page,
|
|
575
|
+
per_page: data.value.per_page,
|
|
576
|
+
total: data.value.total,
|
|
577
|
+
};
|
|
578
|
+
}) as ComputedRef<PaginationMetadata>;
|
|
579
|
+
|
|
580
|
+
const page = computed((): number => {
|
|
581
|
+
if (query.value.page && parseInt(query.value.page + '')) {
|
|
582
|
+
return parseInt(query.value.page + '');
|
|
583
|
+
}
|
|
584
|
+
return 1;
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const lastPage = computed(() => {
|
|
588
|
+
return paginationMetadata.value.last_page;
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const sortField = computed((): string => {
|
|
592
|
+
return query.value.sort?.trim().replace(/^(-)/, '') ?? '';
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const sortDirection = computed((): Direction => {
|
|
596
|
+
if (query.value.sort && query.value.sort.length) {
|
|
597
|
+
if (query.value.sort[0] == '-') {
|
|
598
|
+
return 'desc';
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return 'asc';
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
const searchKeywords = computed((): string => {
|
|
605
|
+
return query.value.search ?? '';
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
/*
|
|
609
|
+
|--------------------------------------------------------------------------
|
|
610
|
+
| Helpers
|
|
611
|
+
|--------------------------------------------------------------------------
|
|
612
|
+
*/
|
|
613
|
+
|
|
614
|
+
/** Scroll into view */
|
|
615
|
+
|
|
616
|
+
const scrollIntoView = () => {
|
|
617
|
+
if (dataIteratorNode.value == null) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
dataIteratorNode.value.scrollIntoView({
|
|
621
|
+
behavior: 'smooth',
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
function updateSearchInput() {
|
|
626
|
+
searchQuery.value = searchKeywords.value;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/*
|
|
630
|
+
|--------------------------------------------------------------------------
|
|
631
|
+
| Created
|
|
632
|
+
|--------------------------------------------------------------------------
|
|
633
|
+
*/
|
|
634
|
+
|
|
635
|
+
let newQuery = cloneDeep(query.value);
|
|
636
|
+
const routeQuery = getRouteQuery();
|
|
637
|
+
|
|
638
|
+
newQuery = merge(newQuery, routeQuery);
|
|
639
|
+
|
|
640
|
+
updateQuery(newQuery);
|
|
641
|
+
|
|
642
|
+
// Update search input on first load
|
|
643
|
+
onMounted(() => {
|
|
644
|
+
updateSearchInput();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
/*
|
|
648
|
+
|--------------------------------------------------------------------------
|
|
649
|
+
| Exposed API
|
|
650
|
+
|--------------------------------------------------------------------------
|
|
651
|
+
*/
|
|
652
|
+
|
|
653
|
+
defineExpose({
|
|
654
|
+
fetch,
|
|
655
|
+
scrollIntoView,
|
|
656
|
+
query,
|
|
657
|
+
});
|
|
658
|
+
</script>
|